1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2012, 2013 Zimbra Software, LLC.
  5  * 
  6  * The contents of this file are subject to the Zimbra Public License
  7  * Version 1.4 ("License"); you may not use this file except in
  8  * compliance with the License.  You may obtain a copy of the License at
  9  * http://www.zimbra.com/license.
 10  * 
 11  * Software distributed under the License is distributed on an "AS IS"
 12  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 13  * ***** END LICENSE BLOCK *****
 14  */
 15 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
 16 (function(win) {
 17 	var whiteSpaceRe = /^\s*|\s*$/g,
 18 		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
 19 
 20 	var tinymce = {
 21 		majorVersion : '3',
 22 
 23 		minorVersion : '5.4.1',
 24 
 25 		releaseDate : '2012-06-24',
 26 
 27 		_init : function() {
 28 			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
 29 
 30 			t.isOpera = win.opera && opera.buildNumber;
 31 
 32 			t.isWebKit = /WebKit/.test(ua);
 33 
 34 			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
 35 
 36 			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
 37 
 38 			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
 39 
 40 			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
 41 
 42 			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
 43 
 44 			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
 45 
 46 			t.isMac = ua.indexOf('Mac') != -1;
 47 
 48 			t.isAir = /adobeair/i.test(ua);
 49 
 50 			t.isIDevice = /(iPad|iPhone)/.test(ua);
 51 			
 52 			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
 53 
 54 			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
 55 			if (win.tinyMCEPreInit) {
 56 				t.suffix = tinyMCEPreInit.suffix;
 57 				t.baseURL = tinyMCEPreInit.base;
 58 				t.query = tinyMCEPreInit.query;
 59 				return;
 60 			}
 61 
 62 			// Get suffix and base
 63 			t.suffix = '';
 64 
 65 			// If base element found, add that infront of baseURL
 66 			nl = d.getElementsByTagName('base');
 67 			for (i=0; i<nl.length; i++) {
 68 				v = nl[i].href;
 69 				if (v) {
 70 					// Host only value like http://site.com or http://site.com:8008
 71 					if (/^https?:\/\/[^\/]+$/.test(v))
 72 						v += '/';
 73 
 74 					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
 75 				}
 76 			}
 77 
 78 			function getBase(n) {
 79 				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
 80 					if (/_(src|dev)\.js/g.test(n.src))
 81 						t.suffix = '_src';
 82 
 83 					if ((p = n.src.indexOf('?')) != -1)
 84 						t.query = n.src.substring(p + 1);
 85 
 86 					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
 87 
 88 					// If path to script is relative and a base href was found add that one infront
 89 					// the src property will always be an absolute one on non IE browsers and IE 8
 90 					// so this logic will basically only be executed on older IE versions
 91 					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
 92 						t.baseURL = base + t.baseURL;
 93 
 94 					return t.baseURL;
 95 				}
 96 
 97 				return null;
 98 			};
 99 
100 			// Check document
101 			nl = d.getElementsByTagName('script');
102 			for (i=0; i<nl.length; i++) {
103 				if (getBase(nl[i]))
104 					return;
105 			}
106 
107 			// Check head
108 			n = d.getElementsByTagName('head')[0];
109 			if (n) {
110 				nl = n.getElementsByTagName('script');
111 				for (i=0; i<nl.length; i++) {
112 					if (getBase(nl[i]))
113 						return;
114 				}
115 			}
116 
117 			return;
118 		},
119 
120 		is : function(o, t) {
121 			if (!t)
122 				return o !== undef;
123 
124 			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
125 				return true;
126 
127 			return typeof(o) == t;
128 		},
129 
130 		makeMap : function(items, delim, map) {
131 			var i;
132 
133 			items = items || [];
134 			delim = delim || ',';
135 
136 			if (typeof(items) == "string")
137 				items = items.split(delim);
138 
139 			map = map || {};
140 
141 			i = items.length;
142 			while (i--)
143 				map[items[i]] = {};
144 
145 			return map;
146 		},
147 
148 		each : function(o, cb, s) {
149 			var n, l;
150 
151 			if (!o)
152 				return 0;
153 
154 			s = s || o;
155 
156 			if (o.length !== undef) {
157 				// Indexed arrays, needed for Safari
158 				for (n=0, l = o.length; n < l; n++) {
159 					if (cb.call(s, o[n], n, o) === false)
160 						return 0;
161 				}
162 			} else {
163 				// Hashtables
164 				for (n in o) {
165 					if (o.hasOwnProperty(n)) {
166 						if (cb.call(s, o[n], n, o) === false)
167 							return 0;
168 					}
169 				}
170 			}
171 
172 			return 1;
173 		},
174 
175 
176 		map : function(a, f) {
177 			var o = [];
178 
179 			tinymce.each(a, function(v) {
180 				o.push(f(v));
181 			});
182 
183 			return o;
184 		},
185 
186 		grep : function(a, f) {
187 			var o = [];
188 
189 			tinymce.each(a, function(v) {
190 				if (!f || f(v))
191 					o.push(v);
192 			});
193 
194 			return o;
195 		},
196 
197 		inArray : function(a, v) {
198 			var i, l;
199 
200 			if (a) {
201 				for (i = 0, l = a.length; i < l; i++) {
202 					if (a[i] === v)
203 						return i;
204 				}
205 			}
206 
207 			return -1;
208 		},
209 
210 		extend : function(obj, ext) {
211 			var i, l, name, args = arguments, value;
212 
213 			for (i = 1, l = args.length; i < l; i++) {
214 				ext = args[i];
215 				for (name in ext) {
216 					if (ext.hasOwnProperty(name)) {
217 						value = ext[name];
218 
219 						if (value !== undef) {
220 							obj[name] = value;
221 						}
222 					}
223 				}
224 			}
225 
226 			return obj;
227 		},
228 
229 
230 		trim : function(s) {
231 			return (s ? '' + s : '').replace(whiteSpaceRe, '');
232 		},
233 
234 		create : function(s, p, root) {
235 			var t = this, sp, ns, cn, scn, c, de = 0;
236 
237 			// Parse : <prefix> <class>:<super class>
238 			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
239 			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
240 
241 			// Create namespace for new class
242 			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
243 
244 			// Class already exists
245 			if (ns[cn])
246 				return;
247 
248 			// Make pure static class
249 			if (s[2] == 'static') {
250 				ns[cn] = p;
251 
252 				if (this.onCreate)
253 					this.onCreate(s[2], s[3], ns[cn]);
254 
255 				return;
256 			}
257 
258 			// Create default constructor
259 			if (!p[cn]) {
260 				p[cn] = function() {};
261 				de = 1;
262 			}
263 
264 			// Add constructor and methods
265 			ns[cn] = p[cn];
266 			t.extend(ns[cn].prototype, p);
267 
268 			// Extend
269 			if (s[5]) {
270 				sp = t.resolve(s[5]).prototype;
271 				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
272 
273 				// Extend constructor
274 				c = ns[cn];
275 				if (de) {
276 					// Add passthrough constructor
277 					ns[cn] = function() {
278 						return sp[scn].apply(this, arguments);
279 					};
280 				} else {
281 					// Add inherit constructor
282 					ns[cn] = function() {
283 						this.parent = sp[scn];
284 						return c.apply(this, arguments);
285 					};
286 				}
287 				ns[cn].prototype[cn] = ns[cn];
288 
289 				// Add super methods
290 				t.each(sp, function(f, n) {
291 					ns[cn].prototype[n] = sp[n];
292 				});
293 
294 				// Add overridden methods
295 				t.each(p, function(f, n) {
296 					// Extend methods if needed
297 					if (sp[n]) {
298 						ns[cn].prototype[n] = function() {
299 							this.parent = sp[n];
300 							return f.apply(this, arguments);
301 						};
302 					} else {
303 						if (n != cn)
304 							ns[cn].prototype[n] = f;
305 					}
306 				});
307 			}
308 
309 			// Add static methods
310 			t.each(p['static'], function(f, n) {
311 				ns[cn][n] = f;
312 			});
313 
314 			if (this.onCreate)
315 				this.onCreate(s[2], s[3], ns[cn].prototype);
316 		},
317 
318 		walk : function(o, f, n, s) {
319 			s = s || this;
320 
321 			if (o) {
322 				if (n)
323 					o = o[n];
324 
325 				tinymce.each(o, function(o, i) {
326 					if (f.call(s, o, i, n) === false)
327 						return false;
328 
329 					tinymce.walk(o, f, n, s);
330 				});
331 			}
332 		},
333 
334 		createNS : function(n, o) {
335 			var i, v;
336 
337 			o = o || win;
338 
339 			n = n.split('.');
340 			for (i=0; i<n.length; i++) {
341 				v = n[i];
342 
343 				if (!o[v])
344 					o[v] = {};
345 
346 				o = o[v];
347 			}
348 
349 			return o;
350 		},
351 
352 		resolve : function(n, o) {
353 			var i, l;
354 
355 			o = o || win;
356 
357 			n = n.split('.');
358 			for (i = 0, l = n.length; i < l; i++) {
359 				o = o[n[i]];
360 
361 				if (!o)
362 					break;
363 			}
364 
365 			return o;
366 		},
367 
368 		addUnload : function(f, s) {
369 			var t = this, unload;
370 
371 			unload = function() {
372 				var li = t.unloads, o, n;
373 
374 				if (li) {
375 					// Call unload handlers
376 					for (n in li) {
377 						o = li[n];
378 
379 						if (o && o.func)
380 							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
381 					}
382 
383 					// Detach unload function
384 					if (win.detachEvent) {
385 						win.detachEvent('onbeforeunload', fakeUnload);
386 						win.detachEvent('onunload', unload);
387 					} else if (win.removeEventListener)
388 						win.removeEventListener('unload', unload, false);
389 
390 					// Destroy references
391 					t.unloads = o = li = w = unload = 0;
392 
393 					// Run garbarge collector on IE
394 					if (win.CollectGarbage)
395 						CollectGarbage();
396 				}
397 			};
398 
399 			function fakeUnload() {
400 				var d = document;
401 
402 				function stop() {
403 					// Prevent memory leak
404 					d.detachEvent('onstop', stop);
405 
406 					// Call unload handler
407 					if (unload)
408 						unload();
409 
410 					d = 0;
411 				};
412 
413 				// Is there things still loading, then do some magic
414 				if (d.readyState == 'interactive') {
415 					// Fire unload when the currently loading page is stopped
416 					if (d)
417 						d.attachEvent('onstop', stop);
418 
419 					// Remove onstop listener after a while to prevent the unload function
420 					// to execute if the user presses cancel in an onbeforeunload
421 					// confirm dialog and then presses the browser stop button
422 					win.setTimeout(function() {
423 						if (d)
424 							d.detachEvent('onstop', stop);
425 					}, 0);
426 				}
427 			};
428 
429 			f = {func : f, scope : s || this};
430 
431 			if (!t.unloads) {
432 				// Attach unload handler
433 				if (win.attachEvent) {
434 					win.attachEvent('onunload', unload);
435 					win.attachEvent('onbeforeunload', fakeUnload);
436 				} else if (win.addEventListener)
437 					win.addEventListener('unload', unload, false);
438 
439 				// Setup initial unload handler array
440 				t.unloads = [f];
441 			} else
442 				t.unloads.push(f);
443 
444 			return f;
445 		},
446 
447 		removeUnload : function(f) {
448 			var u = this.unloads, r = null;
449 
450 			tinymce.each(u, function(o, i) {
451 				if (o && o.func == f) {
452 					u.splice(i, 1);
453 					r = f;
454 					return false;
455 				}
456 			});
457 
458 			return r;
459 		},
460 
461 		explode : function(s, d) {
462 			if (!s || tinymce.is(s, 'array')) {
463 				return s;
464 			}
465 
466 			return tinymce.map(s.split(d || ','), tinymce.trim);
467 		},
468 
469 		_addVer : function(u) {
470 			var v;
471 
472 			if (!this.query)
473 				return u;
474 
475 			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
476 
477 			if (u.indexOf('#') == -1)
478 				return u + v;
479 
480 			return u.replace('#', v + '#');
481 		},
482 
483 		// Fix function for IE 9 where regexps isn't working correctly
484 		// Todo: remove me once MS fixes the bug
485 		_replace : function(find, replace, str) {
486 			// On IE9 we have to fake $x replacement
487 			if (isRegExpBroken) {
488 				return str.replace(find, function() {
489 					var val = replace, args = arguments, i;
490 
491 					for (i = 0; i < args.length - 2; i++) {
492 						if (args[i] === undef) {
493 							val = val.replace(new RegExp('\\$' + i, 'g'), '');
494 						} else {
495 							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
496 						}
497 					}
498 
499 					return val;
500 				});
501 			}
502 
503 			return str.replace(find, replace);
504 		}
505 
506 		};
507 
508 	// Initialize the API
509 	tinymce._init();
510 
511 	// Expose tinymce namespace to the global namespace (window)
512 	win.tinymce = win.tinyMCE = tinymce;
513 
514 	// Describe the different namespaces
515 
516 	})(window);
517 
518 
519 
520 (function() {
521 	if (!window.Prototype)
522 		return alert("Load prototype first!");
523 
524 	// Patch in core NS functions
525 	tinymce.extend(tinymce, {
526 		trim : function(s) {return s ? s.strip() : '';},
527 		inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;}
528 	});
529 
530 	// Patch in functions in various clases
531 	// Add a "#ifndefjquery" statement around each core API function you add below
532 	var patches = {
533 		'tinymce.util.JSON' : {
534 			/*serialize : function(o) {
535 				return o.toJSON();
536 			}*/
537 		}
538 	};
539 
540 	// Patch functions after a class is created
541 	tinymce.onCreate = function(ty, c, p) {
542 		tinymce.extend(p, patches[c]);
543 	};
544 })();
545 
546 
547 tinymce.create('tinymce.util.Dispatcher', {
548 	scope : null,
549 	listeners : null,
550 	inDispatch: false,
551 
552 	Dispatcher : function(scope) {
553 		this.scope = scope || this;
554 		this.listeners = [];
555 	},
556 
557 	add : function(callback, scope) {
558 		this.listeners.push({cb : callback, scope : scope || this.scope});
559 
560 		return callback;
561 	},
562 
563 	addToTop : function(callback, scope) {
564 		var self = this, listener = {cb : callback, scope : scope || self.scope};
565 
566 		// Create new listeners if addToTop is executed in a dispatch loop
567 		if (self.inDispatch) {
568 			self.listeners = [listener].concat(self.listeners);
569 		} else {
570 			self.listeners.unshift(listener);
571 		}
572 
573 		return callback;
574 	},
575 
576 	remove : function(callback) {
577 		var listeners = this.listeners, output = null;
578 
579 		tinymce.each(listeners, function(listener, i) {
580 			if (callback == listener.cb) {
581 				output = listener;
582 				listeners.splice(i, 1);
583 				return false;
584 			}
585 		});
586 
587 		return output;
588 	},
589 
590 	dispatch : function() {
591 		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
592 
593 		self.inDispatch = true;
594 		
595 		// Needs to be a real loop since the listener count might change while looping
596 		// And this is also more efficient
597 		for (i = 0; i < listeners.length; i++) {
598 			listener = listeners[i];
599 			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
600 
601 			if (returnValue === false)
602 				break;
603 		}
604 
605 		self.inDispatch = false;
606 
607 		return returnValue;
608 	}
609 
610 	});
611 
612 (function() {
613 	var each = tinymce.each;
614 
615 	tinymce.create('tinymce.util.URI', {
616 		URI : function(u, s) {
617 			var t = this, o, a, b, base_url;
618 
619 			// Trim whitespace
620 			u = tinymce.trim(u);
621 
622 			// Default settings
623 			s = t.settings = s || {};
624 
625 			// Strange app protocol that isn't http/https or local anchor
626 			// For example: mailto,skype,tel etc.
627 			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
628 				t.source = u;
629 				return;
630 			}
631 
632 			// Absolute path with no host, fake host and protocol
633 			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
634 				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
635 
636 			// Relative path http:// or protocol relative //path
637 			if (!/^[\w\-]*:?\/\//.test(u)) {
638 				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
639 				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
640 			}
641 
642 			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
643 			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
644 			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
645 			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
646 				var s = u[i];
647 
648 				// Zope 3 workaround, they use @@something
649 				if (s)
650 					s = s.replace(/\(mce_at\)/g, '@@');
651 
652 				t[v] = s;
653 			});
654 
655 			b = s.base_uri;
656 			if (b) {
657 				if (!t.protocol)
658 					t.protocol = b.protocol;
659 
660 				if (!t.userInfo)
661 					t.userInfo = b.userInfo;
662 
663 				if (!t.port && t.host === 'mce_host')
664 					t.port = b.port;
665 
666 				if (!t.host || t.host === 'mce_host')
667 					t.host = b.host;
668 
669 				t.source = '';
670 			}
671 
672 			//t.path = t.path || '/';
673 		},
674 
675 		setPath : function(p) {
676 			var t = this;
677 
678 			p = /^(.*?)\/?(\w+)?$/.exec(p);
679 
680 			// Update path parts
681 			t.path = p[0];
682 			t.directory = p[1];
683 			t.file = p[2];
684 
685 			// Rebuild source
686 			t.source = '';
687 			t.getURI();
688 		},
689 
690 		toRelative : function(u) {
691 			var t = this, o;
692 
693 			if (u === "./")
694 				return u;
695 
696 			u = new tinymce.util.URI(u, {base_uri : t});
697 
698 			// Not on same domain/port or protocol
699 			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
700 				return u.getURI();
701 
702 			var tu = t.getURI(), uu = u.getURI();
703 			
704 			// Allow usage of the base_uri when relative_urls = true
705 			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
706 				return tu;
707 
708 			o = t.toRelPath(t.path, u.path);
709 
710 			// Add query
711 			if (u.query)
712 				o += '?' + u.query;
713 
714 			// Add anchor
715 			if (u.anchor)
716 				o += '#' + u.anchor;
717 
718 			return o;
719 		},
720 	
721 		toAbsolute : function(u, nh) {
722 			u = new tinymce.util.URI(u, {base_uri : this});
723 
724 			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
725 		},
726 
727 		toRelPath : function(base, path) {
728 			var items, bp = 0, out = '', i, l;
729 
730 			// Split the paths
731 			base = base.substring(0, base.lastIndexOf('/'));
732 			base = base.split('/');
733 			items = path.split('/');
734 
735 			if (base.length >= items.length) {
736 				for (i = 0, l = base.length; i < l; i++) {
737 					if (i >= items.length || base[i] != items[i]) {
738 						bp = i + 1;
739 						break;
740 					}
741 				}
742 			}
743 
744 			if (base.length < items.length) {
745 				for (i = 0, l = items.length; i < l; i++) {
746 					if (i >= base.length || base[i] != items[i]) {
747 						bp = i + 1;
748 						break;
749 					}
750 				}
751 			}
752 
753 			if (bp === 1)
754 				return path;
755 
756 			for (i = 0, l = base.length - (bp - 1); i < l; i++)
757 				out += "../";
758 
759 			for (i = bp - 1, l = items.length; i < l; i++) {
760 				if (i != bp - 1)
761 					out += "/" + items[i];
762 				else
763 					out += items[i];
764 			}
765 
766 			return out;
767 		},
768 
769 		toAbsPath : function(base, path) {
770 			var i, nb = 0, o = [], tr, outPath;
771 
772 			// Split paths
773 			tr = /\/$/.test(path) ? '/' : '';
774 			base = base.split('/');
775 			path = path.split('/');
776 
777 			// Remove empty chunks
778 			each(base, function(k) {
779 				if (k)
780 					o.push(k);
781 			});
782 
783 			base = o;
784 
785 			// Merge relURLParts chunks
786 			for (i = path.length - 1, o = []; i >= 0; i--) {
787 				// Ignore empty or .
788 				if (path[i].length === 0 || path[i] === ".")
789 					continue;
790 
791 				// Is parent
792 				if (path[i] === '..') {
793 					nb++;
794 					continue;
795 				}
796 
797 				// Move up
798 				if (nb > 0) {
799 					nb--;
800 					continue;
801 				}
802 
803 				o.push(path[i]);
804 			}
805 
806 			i = base.length - nb;
807 
808 			// If /a/b/c or /
809 			if (i <= 0)
810 				outPath = o.reverse().join('/');
811 			else
812 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
813 
814 			// Add front / if it's needed
815 			if (outPath.indexOf('/') !== 0)
816 				outPath = '/' + outPath;
817 
818 			// Add traling / if it's needed
819 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
820 				outPath += tr;
821 
822 			return outPath;
823 		},
824 
825 		getURI : function(nh) {
826 			var s, t = this;
827 
828 			// Rebuild source
829 			if (!t.source || nh) {
830 				s = '';
831 
832 				if (!nh) {
833 					if (t.protocol)
834 						s += t.protocol + '://';
835 
836 					if (t.userInfo)
837 						s += t.userInfo + '@';
838 
839 					if (t.host)
840 						s += t.host;
841 
842 					if (t.port)
843 						s += ':' + t.port;
844 				}
845 
846 				if (t.path)
847 					s += t.path;
848 
849 				if (t.query)
850 					s += '?' + t.query;
851 
852 				if (t.anchor)
853 					s += '#' + t.anchor;
854 
855 				t.source = s;
856 			}
857 
858 			return t.source;
859 		}
860 	});
861 })();
862 
863 (function() {
864 	var each = tinymce.each;
865 
866 	tinymce.create('static tinymce.util.Cookie', {
867 		getHash : function(n) {
868 			var v = this.get(n), h;
869 
870 			if (v) {
871 				each(v.split('&'), function(v) {
872 					v = v.split('=');
873 					h = h || {};
874 					h[unescape(v[0])] = unescape(v[1]);
875 				});
876 			}
877 
878 			return h;
879 		},
880 
881 		setHash : function(n, v, e, p, d, s) {
882 			var o = '';
883 
884 			each(v, function(v, k) {
885 				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
886 			});
887 
888 			this.set(n, o, e, p, d, s);
889 		},
890 
891 		get : function(n) {
892 			var c = document.cookie, e, p = n + "=", b;
893 
894 			// Strict mode
895 			if (!c)
896 				return;
897 
898 			b = c.indexOf("; " + p);
899 
900 			if (b == -1) {
901 				b = c.indexOf(p);
902 
903 				if (b !== 0)
904 					return null;
905 			} else
906 				b += 2;
907 
908 			e = c.indexOf(";", b);
909 
910 			if (e == -1)
911 				e = c.length;
912 
913 			return unescape(c.substring(b + p.length, e));
914 		},
915 
916 		set : function(n, v, e, p, d, s) {
917 			document.cookie = n + "=" + escape(v) +
918 				((e) ? "; expires=" + e.toGMTString() : "") +
919 				((p) ? "; path=" + escape(p) : "") +
920 				((d) ? "; domain=" + d : "") +
921 				((s) ? "; secure" : "");
922 		},
923 
924 		remove : function(name, path, domain) {
925 			var date = new Date();
926 
927 			date.setTime(date.getTime() - 1000);
928 
929 			this.set(name, '', date, path, domain);
930 		}
931 	});
932 })();
933 
934 (function() {
935 	function serialize(o, quote) {
936 		var i, v, t, name;
937 
938 		quote = quote || '"';
939 
940 		if (o == null)
941 			return 'null';
942 
943 		t = typeof o;
944 
945 		if (t == 'string') {
946 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
947 
948 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
949 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
950 				if (quote === '"' && a === "'")
951 					return a;
952 
953 				i = v.indexOf(b);
954 
955 				if (i + 1)
956 					return '\\' + v.charAt(i + 1);
957 
958 				a = b.charCodeAt().toString(16);
959 
960 				return '\\u' + '0000'.substring(a.length) + a;
961 			}) + quote;
962 		}
963 
964 		if (t == 'object') {
965 			if (o.hasOwnProperty && o instanceof Array) {
966 					for (i=0, v = '['; i<o.length; i++)
967 						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
968 
969 					return v + ']';
970 				}
971 
972 				v = '{';
973 
974 				for (name in o) {
975 					if (o.hasOwnProperty(name)) {
976 						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
977 					}
978 				}
979 
980 				return v + '}';
981 		}
982 
983 		return '' + o;
984 	};
985 
986 	tinymce.util.JSON = {
987 		serialize: serialize,
988 
989 		parse: function(s) {
990 			try {
991 				return eval('(' + s + ')');
992 			} catch (ex) {
993 				// Ignore
994 			}
995 		}
996 
997 		};
998 })();
999 
1000 tinymce.create('static tinymce.util.XHR', {
1001 	send : function(o) {
1002 		var x, t, w = window, c = 0;
1003 
1004 		function ready() {
1005 			if (!o.async || x.readyState == 4 || c++ > 10000) {
1006 				if (o.success && c < 10000 && x.status == 200)
1007 					o.success.call(o.success_scope, '' + x.responseText, x, o);
1008 				else if (o.error)
1009 					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
1010 
1011 				x = null;
1012 			} else
1013 				w.setTimeout(ready, 10);
1014 		};
1015 
1016 		// Default settings
1017 		o.scope = o.scope || this;
1018 		o.success_scope = o.success_scope || o.scope;
1019 		o.error_scope = o.error_scope || o.scope;
1020 		o.async = o.async === false ? false : true;
1021 		o.data = o.data || '';
1022 
1023 		function get(s) {
1024 			x = 0;
1025 
1026 			try {
1027 				x = new ActiveXObject(s);
1028 			} catch (ex) {
1029 			}
1030 
1031 			return x;
1032 		};
1033 
1034 		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1035 
1036 		if (x) {
1037 			if (x.overrideMimeType)
1038 				x.overrideMimeType(o.content_type);
1039 
1040 			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1041 
1042 			if (o.content_type)
1043 				x.setRequestHeader('Content-Type', o.content_type);
1044 
1045 			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1046 
1047 			x.send(o.data);
1048 
1049 			// Syncronous request
1050 			if (!o.async)
1051 				return ready();
1052 
1053 			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1054 			t = w.setTimeout(ready, 10);
1055 		}
1056 	}
1057 });
1058 
1059 (function() {
1060 	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1061 
1062 	tinymce.create('tinymce.util.JSONRequest', {
1063 		JSONRequest : function(s) {
1064 			this.settings = extend({
1065 			}, s);
1066 			this.count = 0;
1067 		},
1068 
1069 		send : function(o) {
1070 			var ecb = o.error, scb = o.success;
1071 
1072 			o = extend(this.settings, o);
1073 
1074 			o.success = function(c, x) {
1075 				c = JSON.parse(c);
1076 
1077 				if (typeof(c) == 'undefined') {
1078 					c = {
1079 						error : 'JSON Parse error.'
1080 					};
1081 				}
1082 
1083 				if (c.error)
1084 					ecb.call(o.error_scope || o.scope, c.error, x);
1085 				else
1086 					scb.call(o.success_scope || o.scope, c.result);
1087 			};
1088 
1089 			o.error = function(ty, x) {
1090 				if (ecb)
1091 					ecb.call(o.error_scope || o.scope, ty, x);
1092 			};
1093 
1094 			o.data = JSON.serialize({
1095 				id : o.id || 'c' + (this.count++),
1096 				method : o.method,
1097 				params : o.params
1098 			});
1099 
1100 			// JSON content type for Ruby on rails. Bug: #1883287
1101 			o.content_type = 'application/json';
1102 
1103 			XHR.send(o);
1104 		},
1105 
1106 		'static' : {
1107 			sendRPC : function(o) {
1108 				return new tinymce.util.JSONRequest().send(o);
1109 			}
1110 		}
1111 	});
1112 }());
1113 (function(tinymce){
1114 	tinymce.VK = {
1115 		BACKSPACE: 8,
1116 		DELETE: 46,
1117 		DOWN: 40,
1118 		ENTER: 13,
1119 		LEFT: 37,
1120 		RIGHT: 39,
1121 		SPACEBAR: 32,
1122 		TAB: 9,
1123 		UP: 38,
1124 
1125 		modifierPressed: function (e) {
1126 			return e.shiftKey || e.ctrlKey || e.altKey;
1127 		},
1128 
1129 		metaKeyPressed: function(e) {
1130 			return tinymce.isMac ? e.metaKey : e.ctrlKey;
1131 		}
1132 	};
1133 })(tinymce);
1134 
1135 tinymce.util.Quirks = function(editor) {
1136 	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
1137 
1138 	function setEditorCommandState(cmd, state) {
1139 		try {
1140 			editor.getDoc().execCommand(cmd, false, state);
1141 		} catch (ex) {
1142 			// Ignore
1143 		}
1144 	}
1145 
1146 	function getDocumentMode() {
1147 		var documentMode = editor.getDoc().documentMode;
1148 
1149 		return documentMode ? documentMode : 6;
1150 	};
1151 
1152 	function cleanupStylesWhenDeleting() {
1153 		function removeMergedFormatSpans(isDelete) {
1154 			var rng, blockElm, node, clonedSpan;
1155 
1156 			rng = selection.getRng();
1157 
1158 			// Find root block
1159 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1160 
1161 			// On delete clone the root span of the next block element
1162 			if (isDelete)
1163 				blockElm = dom.getNext(blockElm, dom.isBlock);
1164 
1165 			// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1166 			if (blockElm) {
1167 				node = blockElm.firstChild;
1168 
1169 				// Ignore empty text nodes
1170 				while (node && node.nodeType == 3 && node.nodeValue.length === 0)
1171 					node = node.nextSibling;
1172 
1173 				if (node && node.nodeName === 'SPAN') {
1174 					clonedSpan = node.cloneNode(false);
1175 				}
1176 			}
1177 
1178 			// Do the backspace/delete action
1179 			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1180 
1181 			// Find all odd apple-style-spans
1182 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1183 			tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1184 				var bm = selection.getBookmark();
1185 
1186 				if (clonedSpan) {
1187 					dom.replace(clonedSpan.cloneNode(false), span, true);
1188 				} else {
1189 					dom.remove(span, true);
1190 				}
1191 
1192 				// Restore the selection
1193 				selection.moveToBookmark(bm);
1194 			});
1195 		};
1196 
1197 		editor.onKeyDown.add(function(editor, e) {
1198 			var isDelete;
1199 
1200 			isDelete = e.keyCode == DELETE;
1201 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1202 				e.preventDefault();
1203 				removeMergedFormatSpans(isDelete);
1204 			}
1205 		});
1206 
1207 		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1208 	};
1209 	
1210 	function emptyEditorWhenDeleting() {
1211 		function serializeRng(rng) {
1212 			var body = dom.create("body");
1213 			var contents = rng.cloneContents();
1214 			body.appendChild(contents);
1215 			return selection.serializer.serialize(body, {format: 'html'});
1216 		}
1217 
1218 		function allContentsSelected(rng) {
1219 			var selection = serializeRng(rng);
1220 
1221 			var allRng = dom.createRng();
1222 			allRng.selectNode(editor.getBody());
1223 
1224 			var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
1225 			return selection === allSelection;
1226 		}
1227 
1228 		editor.onKeyDown.add(function(editor, e) {
1229 			var keyCode = e.keyCode, isCollapsed;
1230 
1231 			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1232 			if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1233 				isCollapsed = editor.selection.isCollapsed();
1234 
1235 				// Selection is collapsed but the editor isn't empty
1236 				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1237 					return;
1238 				}
1239 
1240 				// IE deletes all contents correctly when everything is selected
1241 				if (tinymce.isIE && !isCollapsed) {
1242 					return;
1243 				}
1244 
1245 				// Selection isn't collapsed but not all the contents is selected
1246 				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1247 					return;
1248 				}
1249 
1250 				// Manually empty the editor
1251 				editor.setContent('');
1252 				editor.selection.setCursorLocation(editor.getBody(), 0);
1253 				editor.nodeChanged();
1254 			}
1255 		});
1256 	};
1257 
1258 	function selectAll() {
1259 		editor.onKeyDown.add(function(editor, e) {
1260 			if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
1261 				e.preventDefault();
1262 				editor.execCommand('SelectAll');
1263 			}
1264 		});
1265 	};
1266 
1267 	function inputMethodFocus() {
1268 		if (!editor.settings.content_editable) {
1269 			// Case 1 IME doesn't initialize if you focus the document
1270 			dom.bind(editor.getDoc(), 'focusin', function(e) {
1271 				selection.setRng(selection.getRng());
1272 			});
1273 
1274 			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1275 			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1276 				if (e.target == editor.getDoc().documentElement) {
1277 					editor.getWin().focus();
1278 					selection.setRng(selection.getRng());
1279 				}
1280 			});
1281 		}
1282 	};
1283 
1284 	function removeHrOnBackspace() {
1285 		editor.onKeyDown.add(function(editor, e) {
1286 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1287 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1288 					var node = selection.getNode();
1289 					var previousSibling = node.previousSibling;
1290 
1291 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1292 						dom.remove(previousSibling);
1293 						tinymce.dom.Event.cancel(e);
1294 					}
1295 				}
1296 			}
1297 		})
1298 	}
1299 
1300 	function focusBody() {
1301 		// Fix for a focus bug in FF 3.x where the body element
1302 		// wouldn't get proper focus if the user clicked on the HTML element
1303 		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1304 			editor.onMouseDown.add(function(editor, e) {
1305 				if (e.target.nodeName === "HTML") {
1306 					var body = editor.getBody();
1307 
1308 					// Blur the body it's focused but not correctly focused
1309 					body.blur();
1310 
1311 					// Refocus the body after a little while
1312 					setTimeout(function() {
1313 						body.focus();
1314 					}, 0);
1315 				}
1316 			});
1317 		}
1318 	};
1319 
1320 	function selectControlElements() {
1321 		editor.onClick.add(function(editor, e) {
1322 			e = e.target;
1323 
1324 			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1325 			// WebKit can't even do simple things like selecting an image
1326 			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1327 			if (/^(IMG|HR)$/.test(e.nodeName)) {
1328 				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1329 			}
1330 
1331 			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1332 				selection.select(e);
1333 			}
1334 
1335 			editor.nodeChanged();
1336 		});
1337 	};
1338 
1339 	function removeStylesWhenDeletingAccrossBlockElements() {
1340 		function getAttributeApplyFunction() {
1341 			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1342 
1343 			return function() {
1344 				var target = selection.getStart();
1345 
1346 				if (target !== editor.getBody()) {
1347 					dom.setAttrib(target, "style", null);
1348 
1349 					tinymce.each(template, function(attr) {
1350 						target.setAttributeNode(attr.cloneNode(true));
1351 					});
1352 				}
1353 			};
1354 		}
1355 
1356 		function isSelectionAcrossElements() {
1357 			return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1358 		}
1359 
1360 		function blockEvent(editor, e) {
1361 			e.preventDefault();
1362 			return false;
1363 		}
1364 
1365 		editor.onKeyPress.add(function(editor, e) {
1366 			var applyAttributes;
1367 
1368 			if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1369 				applyAttributes = getAttributeApplyFunction();
1370 				editor.getDoc().execCommand('delete', false, null);
1371 				applyAttributes();
1372 				e.preventDefault();
1373 				return false;
1374 			}
1375 		});
1376 
1377 		dom.bind(editor.getDoc(), 'cut', function(e) {
1378 			var applyAttributes;
1379 
1380 			if (isSelectionAcrossElements()) {
1381 				applyAttributes = getAttributeApplyFunction();
1382 				editor.onKeyUp.addToTop(blockEvent);
1383 
1384 				setTimeout(function() {
1385 					applyAttributes();
1386 					editor.onKeyUp.remove(blockEvent);
1387 				}, 0);
1388 			}
1389 		});
1390 	}
1391 
1392 	function selectionChangeNodeChanged() {
1393 		var lastRng, selectionTimer;
1394 
1395 		dom.bind(editor.getDoc(), 'selectionchange', function() {
1396 			if (selectionTimer) {
1397 				clearTimeout(selectionTimer);
1398 				selectionTimer = 0;
1399 			}
1400 
1401 			selectionTimer = window.setTimeout(function() {
1402 				var rng = selection.getRng();
1403 
1404 				// Compare the ranges to see if it was a real change or not
1405 				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1406 					editor.nodeChanged();
1407 					lastRng = rng;
1408 				}
1409 			}, 50);
1410 		});
1411 	}
1412 
1413 	function ensureBodyHasRoleApplication() {
1414 		document.body.setAttribute("role", "application");
1415 	}
1416 
1417 	function disableBackspaceIntoATable() {
1418 		editor.onKeyDown.add(function(editor, e) {
1419 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1420 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1421 					var previousSibling = selection.getNode().previousSibling;
1422 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1423 						return tinymce.dom.Event.cancel(e);
1424 					}
1425 				}
1426 			}
1427 		})
1428 	}
1429 
1430 	function addNewLinesBeforeBrInPre() {
1431 		// IE8+ rendering mode does the right thing with BR in PRE
1432 		if (getDocumentMode() > 7) {
1433 			return;
1434 		}
1435 
1436 		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1437 		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1438 		setEditorCommandState('RespectVisibilityInDesign', true);
1439 		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1440 		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1441 
1442 		// Adds a \n before all BR elements in PRE to get them visual
1443 		editor.parser.addNodeFilter('pre', function(nodes, name) {
1444 			var i = nodes.length, brNodes, j, brElm, sibling;
1445 
1446 			while (i--) {
1447 				brNodes = nodes[i].getAll('br');
1448 				j = brNodes.length;
1449 				while (j--) {
1450 					brElm = brNodes[j];
1451 
1452 					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1453 					sibling = brElm.prev;
1454 					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1455 						sibling.value += '\n';
1456 					} else {
1457 						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1458 					}
1459 				}
1460 			}
1461 		});
1462 
1463 		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1464 		editor.serializer.addNodeFilter('pre', function(nodes, name) {
1465 			var i = nodes.length, brNodes, j, brElm, sibling;
1466 
1467 			while (i--) {
1468 				brNodes = nodes[i].getAll('br');
1469 				j = brNodes.length;
1470 				while (j--) {
1471 					brElm = brNodes[j];
1472 					sibling = brElm.prev;
1473 					if (sibling && sibling.type == 3) {
1474 						sibling.value = sibling.value.replace(/\r?\n$/, '');
1475 					}
1476 				}
1477 			}
1478 		});
1479 	}
1480 
1481 	function removePreSerializedStylesWhenSelectingControls() {
1482 		dom.bind(editor.getBody(), 'mouseup', function(e) {
1483 			var value, node = selection.getNode();
1484 
1485 			// Moved styles to attributes on IMG eements
1486 			if (node.nodeName == 'IMG') {
1487 				// Convert style width to width attribute
1488 				if (value = dom.getStyle(node, 'width')) {
1489 					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1490 					dom.setStyle(node, 'width', '');
1491 				}
1492 
1493 				// Convert style height to height attribute
1494 				if (value = dom.getStyle(node, 'height')) {
1495 					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1496 					dom.setStyle(node, 'height', '');
1497 				}
1498 			}
1499 		});
1500 	}
1501 
1502 	function keepInlineElementOnDeleteBackspace() {
1503 		editor.onKeyDown.add(function(editor, e) {
1504 			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1505 
1506 			isDelete = e.keyCode == DELETE;
1507 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1508 				rng = selection.getRng();
1509 				container = rng.startContainer;
1510 				offset = rng.startOffset;
1511 				collapsed = rng.collapsed;
1512 
1513 				// Override delete if the start container is a text node and is at the beginning of text or
1514 				// just before/after the last character to be deleted in collapsed mode
1515 				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1516 					nonEmptyElements = editor.schema.getNonEmptyElements();
1517 
1518 					// Prevent default logic since it's broken
1519 					e.preventDefault();
1520 
1521 					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1522 					brElm = dom.create('br', {id: '__tmp'});
1523 					container.parentNode.insertBefore(brElm, container);
1524 
1525 					// Do the browser delete
1526 					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1527 
1528 					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1529 					container = selection.getRng().startContainer;
1530 					sibling = container.previousSibling;
1531 					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1532 						dom.remove(sibling);
1533 					}
1534 
1535 					// Remove the temp element we inserted
1536 					dom.remove('__tmp');
1537 				}
1538 			}
1539 		});
1540 	}
1541 
1542 	function removeBlockQuoteOnBackSpace() {
1543 		// Add block quote deletion handler
1544 		editor.onKeyDown.add(function(editor, e) {
1545 			var rng, container, offset, root, parent;
1546 
1547 			if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1548 				return;
1549 			}
1550 
1551 			rng = selection.getRng();
1552 			container = rng.startContainer;
1553 			offset = rng.startOffset;
1554 			root = dom.getRoot();
1555 			parent = container;
1556 
1557 			if (!rng.collapsed || offset !== 0) {
1558 				return;
1559 			}
1560 
1561 			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1562 				parent = parent.parentNode;
1563 			}
1564 
1565 			// Is the cursor at the beginning of a blockquote?
1566 			if (parent.tagName === 'BLOCKQUOTE') {
1567 				// Remove the blockquote
1568 				editor.formatter.toggle('blockquote', null, parent);
1569 
1570 				// Move the caret to the beginning of container
1571 				rng.setStart(container, 0);
1572 				rng.setEnd(container, 0);
1573 				selection.setRng(rng);
1574 				selection.collapse(false);
1575 			}
1576 		});
1577 	};
1578 
1579 	function setGeckoEditingOptions() {
1580 		function setOpts() {
1581 			editor._refreshContentEditable();
1582 
1583 			setEditorCommandState("StyleWithCSS", false);
1584 			setEditorCommandState("enableInlineTableEditing", false);
1585 
1586 			if (!settings.object_resizing) {
1587 				setEditorCommandState("enableObjectResizing", false);
1588 			}
1589 		};
1590 
1591 		if (!settings.readonly) {
1592 			editor.onBeforeExecCommand.add(setOpts);
1593 			editor.onMouseDown.add(setOpts);
1594 		}
1595 	};
1596 
1597 	function addBrAfterLastLinks() {
1598 		function fixLinks(editor, o) {
1599 			tinymce.each(dom.select('a'), function(node) {
1600 				var parentNode = node.parentNode, root = dom.getRoot();
1601 
1602 				if (parentNode.lastChild === node) {
1603 					while (parentNode && !dom.isBlock(parentNode)) {
1604 						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1605 							return;
1606 						}
1607 
1608 						parentNode = parentNode.parentNode;
1609 					}
1610 
1611 					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1612 				}
1613 			});
1614 		};
1615 
1616 		editor.onExecCommand.add(function(editor, cmd) {
1617 			if (cmd === 'CreateLink') {
1618 				fixLinks(editor);
1619 			}
1620 		});
1621 
1622 		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1623 	};
1624 
1625 	function setDefaultBlockType() {
1626 		if (settings.forced_root_block) {
1627 			editor.onInit.add(function() {
1628 				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1629 			});
1630 		}
1631 	}
1632 
1633 	function removeGhostSelection() {
1634 		function repaint(sender, args) {
1635 			if (!sender || !args.initial) {
1636 				editor.execCommand('mceRepaint');
1637 			}
1638 		};
1639 
1640 		editor.onUndo.add(repaint);
1641 		editor.onRedo.add(repaint);
1642 		editor.onSetContent.add(repaint);
1643 	};
1644 
1645 	function deleteControlItemOnBackSpace() {
1646 		editor.onKeyDown.add(function(editor, e) {
1647 			var rng;
1648 
1649 			if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
1650 				rng = editor.getDoc().selection.createRange();
1651 				if (rng && rng.item) {
1652 					e.preventDefault();
1653 					editor.undoManager.beforeChange();
1654 					dom.remove(rng.item(0));
1655 					editor.undoManager.add();
1656 				}
1657 			}
1658 		});
1659 	};
1660 
1661 	function renderEmptyBlocksFix() {
1662 		var emptyBlocksCSS;
1663 
1664 		// IE10+
1665 		if (getDocumentMode() >= 10) {
1666 			emptyBlocksCSS = '';
1667 			tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1668 				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1669 			});
1670 
1671 			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1672 		}
1673 	};
1674 
1675 	function fakeImageResize() {
1676 		var mouseDownImg, startX, startY, startW, startH;
1677 
1678 		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1679 			return;
1680 		}
1681 
1682 		editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
1683 
1684 		function resizeImage(e) {
1685 			var deltaX, deltaY, ratio, width, height;
1686 
1687 			if (mouseDownImg) {
1688 				deltaX = e.screenX - startX;
1689 				deltaY = e.screenY - startY;
1690 				ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
1691 
1692 				// Only update styles if the user draged one pixel or more
1693 				if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
1694 					// Constrain proportions
1695 					width = Math.round(startW * ratio);
1696 					height = Math.round(startH * ratio);
1697 
1698 					// Resize by using style or attribute
1699 					if (mouseDownImg.style.width) {
1700 						dom.setStyle(mouseDownImg, 'width', width);
1701 					} else {
1702 						dom.setAttrib(mouseDownImg, 'width', width);
1703 					}
1704 
1705 					// Resize by using style or attribute
1706 					if (mouseDownImg.style.height) {
1707 						dom.setStyle(mouseDownImg, 'height', height);
1708 					} else {
1709 						dom.setAttrib(mouseDownImg, 'height', height);
1710 					}
1711 
1712 					if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
1713 						dom.addClass(editor.getBody(), 'mceResizeImages');
1714 					}
1715 				}
1716 			}
1717 		};
1718 
1719 		editor.onMouseDown.add(function(editor, e) {
1720 			var target = e.target;
1721 
1722 			if (target.nodeName == "IMG") {
1723 				mouseDownImg = target;
1724 				startX = e.screenX;
1725 				startY = e.screenY;
1726 				startW = mouseDownImg.clientWidth;
1727 				startH = mouseDownImg.clientHeight;
1728 				dom.bind(editor.getDoc(), 'mousemove', resizeImage);
1729 				e.preventDefault();
1730 			}
1731 		});
1732 
1733 		// Unbind events on node change and restore resize cursor
1734 		editor.onNodeChange.add(function() {
1735 			if (mouseDownImg) {
1736 				mouseDownImg = null;
1737 				dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
1738 			}
1739 
1740 			if (selection.getNode().nodeName == "IMG") {
1741 				dom.addClass(editor.getBody(), 'mceResizeImages');
1742 			} else {
1743 				dom.removeClass(editor.getBody(), 'mceResizeImages');
1744 			}
1745 		});
1746 	};
1747 
1748 	// All browsers
1749 	disableBackspaceIntoATable();
1750 	removeBlockQuoteOnBackSpace();
1751 	emptyEditorWhenDeleting();
1752 
1753 	// WebKit
1754 	if (tinymce.isWebKit) {
1755 		keepInlineElementOnDeleteBackspace();
1756 		cleanupStylesWhenDeleting();
1757 		inputMethodFocus();
1758 		selectControlElements();
1759 		setDefaultBlockType();
1760 
1761 		// iOS
1762 		if (tinymce.isIDevice) {
1763 			selectionChangeNodeChanged();
1764 		} else {
1765 			fakeImageResize();
1766 			selectAll();
1767 		}
1768 	}
1769 
1770 	// IE
1771 	if (tinymce.isIE) {
1772 		removeHrOnBackspace();
1773 		ensureBodyHasRoleApplication();
1774 		addNewLinesBeforeBrInPre();
1775 		removePreSerializedStylesWhenSelectingControls();
1776 		deleteControlItemOnBackSpace();
1777 		renderEmptyBlocksFix();
1778 	}
1779 
1780 	// Gecko
1781 	if (tinymce.isGecko) {
1782 		removeHrOnBackspace();
1783 		focusBody();
1784 		removeStylesWhenDeletingAccrossBlockElements();
1785 		setGeckoEditingOptions();
1786 		addBrAfterLastLinks();
1787 		removeGhostSelection();
1788 	}
1789 };
1790 (function(tinymce) {
1791 	var namedEntities, baseEntities, reverseEntities,
1792 		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1793 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1794 		rawCharsRegExp = /[<>&\"\']/g,
1795 		entityRegExp = /&(#x|#)?([\w]+);/g,
1796 		asciiMap = {
1797 				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1798 				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1799 				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1800 				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1801 				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1802 		};
1803 
1804 	// Raw entities
1805 	baseEntities = {
1806 		'\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
1807 		"'" : ''',
1808 		'<' : '<',
1809 		'>' : '>',
1810 		'&' : '&'
1811 	};
1812 
1813 	// Reverse lookup table for raw entities
1814 	reverseEntities = {
1815 		'<' : '<',
1816 		'>' : '>',
1817 		'&' : '&',
1818 		'"' : '"',
1819 		''' : "'"
1820 	};
1821 
1822 	// Decodes text by using the browser
1823 	function nativeDecode(text) {
1824 		var elm;
1825 
1826 		elm = document.createElement("div");
1827 		elm.innerHTML = text;
1828 
1829 		return elm.textContent || elm.innerText || text;
1830 	};
1831 
1832 	// Build a two way lookup table for the entities
1833 	function buildEntitiesLookup(items, radix) {
1834 		var i, chr, entity, lookup = {};
1835 
1836 		if (items) {
1837 			items = items.split(',');
1838 			radix = radix || 10;
1839 
1840 			// Build entities lookup table
1841 			for (i = 0; i < items.length; i += 2) {
1842 				chr = String.fromCharCode(parseInt(items[i], radix));
1843 
1844 				// Only add non base entities
1845 				if (!baseEntities[chr]) {
1846 					entity = '&' + items[i + 1] + ';';
1847 					lookup[chr] = entity;
1848 					lookup[entity] = chr;
1849 				}
1850 			}
1851 
1852 			return lookup;
1853 		}
1854 	};
1855 
1856 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
1857 	namedEntities = buildEntitiesLookup(
1858 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1859 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1860 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1861 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1862 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1863 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1864 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1865 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1866 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1867 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1868 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1869 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1870 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1871 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1872 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1873 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1874 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1875 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1876 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1877 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1878 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1879 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1880 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1881 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1882 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
1883 
1884 	tinymce.html = tinymce.html || {};
1885 
1886 	tinymce.html.Entities = {
1887 		encodeRaw : function(text, attr) {
1888 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1889 				return baseEntities[chr] || chr;
1890 			});
1891 		},
1892 
1893 		encodeAllRaw : function(text) {
1894 			return ('' + text).replace(rawCharsRegExp, function(chr) {
1895 				return baseEntities[chr] || chr;
1896 			});
1897 		},
1898 
1899 		encodeNumeric : function(text, attr) {
1900 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1901 				// Multi byte sequence convert it to a single entity
1902 				if (chr.length > 1)
1903 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1904 
1905 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1906 			});
1907 		},
1908 
1909 		encodeNamed : function(text, attr, entities) {
1910 			entities = entities || namedEntities;
1911 
1912 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1913 				return baseEntities[chr] || entities[chr] || chr;
1914 			});
1915 		},
1916 
1917 		getEncodeFunc : function(name, entities) {
1918 			var Entities = tinymce.html.Entities;
1919 
1920 			entities = buildEntitiesLookup(entities) || namedEntities;
1921 
1922 			function encodeNamedAndNumeric(text, attr) {
1923 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1924 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1925 				});
1926 			};
1927 
1928 			function encodeCustomNamed(text, attr) {
1929 				return Entities.encodeNamed(text, attr, entities);
1930 			};
1931 
1932 			// Replace + with , to be compatible with previous TinyMCE versions
1933 			name = tinymce.makeMap(name.replace(/\+/g, ','));
1934 
1935 			// Named and numeric encoder
1936 			if (name.named && name.numeric)
1937 				return encodeNamedAndNumeric;
1938 
1939 			// Named encoder
1940 			if (name.named) {
1941 				// Custom names
1942 				if (entities)
1943 					return encodeCustomNamed;
1944 
1945 				return Entities.encodeNamed;
1946 			}
1947 
1948 			// Numeric
1949 			if (name.numeric)
1950 				return Entities.encodeNumeric;
1951 
1952 			// Raw encoder
1953 			return Entities.encodeRaw;
1954 		},
1955 
1956 		decode : function(text) {
1957 			return text.replace(entityRegExp, function(all, numeric, value) {
1958 				if (numeric) {
1959 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
1960 
1961 					// Support upper UTF
1962 					if (value > 0xFFFF) {
1963 						value -= 0x10000;
1964 
1965 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1966 					} else
1967 						return asciiMap[value] || String.fromCharCode(value);
1968 				}
1969 
1970 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1971 			});
1972 		}
1973 	};
1974 })(tinymce);
1975 
1976 tinymce.html.Styles = function(settings, schema) {
1977 	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1978 		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1979 		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1980 		trimRightRegExp = /\s+$/,
1981 		urlColorRegExp = /rgb/,
1982 		undef, i, encodingLookup = {}, encodingItems;
1983 
1984 	settings = settings || {};
1985 
1986 	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1987 	for (i = 0; i < encodingItems.length; i++) {
1988 		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1989 		encodingLookup['\uFEFF' + i] = encodingItems[i];
1990 	}
1991 
1992 	function toHex(match, r, g, b) {
1993 		function hex(val) {
1994 			val = parseInt(val).toString(16);
1995 
1996 			return val.length > 1 ? val : '0' + val; // 0 -> 00
1997 		};
1998 
1999 		return '#' + hex(r) + hex(g) + hex(b);
2000 	};
2001 
2002 	return {
2003 		toHex : function(color) {
2004 			return color.replace(rgbRegExp, toHex);
2005 		},
2006 
2007 		parse : function(css) {
2008 			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
2009 
2010 			function compress(prefix, suffix) {
2011 				var top, right, bottom, left;
2012 
2013 				// Get values and check it it needs compressing
2014 				top = styles[prefix + '-top' + suffix];
2015 				if (!top)
2016 					return;
2017 
2018 				right = styles[prefix + '-right' + suffix];
2019 				if (top != right)
2020 					return;
2021 
2022 				bottom = styles[prefix + '-bottom' + suffix];
2023 				if (right != bottom)
2024 					return;
2025 
2026 				left = styles[prefix + '-left' + suffix];
2027 				if (bottom != left)
2028 					return;
2029 
2030 				// Compress
2031 				styles[prefix + suffix] = left;
2032 				delete styles[prefix + '-top' + suffix];
2033 				delete styles[prefix + '-right' + suffix];
2034 				delete styles[prefix + '-bottom' + suffix];
2035 				delete styles[prefix + '-left' + suffix];
2036 			};
2037 
2038 			function canCompress(key) {
2039 				var value = styles[key], i;
2040 
2041 				if (!value || value.indexOf(' ') < 0)
2042 					return;
2043 
2044 				value = value.split(' ');
2045 				i = value.length;
2046 				while (i--) {
2047 					if (value[i] !== value[0])
2048 						return false;
2049 				}
2050 
2051 				styles[key] = value[0];
2052 
2053 				return true;
2054 			};
2055 
2056 			function compress2(target, a, b, c) {
2057 				if (!canCompress(a))
2058 					return;
2059 
2060 				if (!canCompress(b))
2061 					return;
2062 
2063 				if (!canCompress(c))
2064 					return;
2065 
2066 				// Compress
2067 				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2068 				delete styles[a];
2069 				delete styles[b];
2070 				delete styles[c];
2071 			};
2072 
2073 			// Encodes the specified string by replacing all \" \' ; : with _<num>
2074 			function encode(str) {
2075 				isEncoded = true;
2076 
2077 				return encodingLookup[str];
2078 			};
2079 
2080 			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2081 			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2082 			function decode(str, keep_slashes) {
2083 				if (isEncoded) {
2084 					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2085 						return encodingLookup[str];
2086 					});
2087 				}
2088 
2089 				if (!keep_slashes)
2090 					str = str.replace(/\\([\'\";:])/g, "$1");
2091 
2092 				return str;
2093 			};
2094 
2095 			function processUrl(match, url, url2, url3, str, str2) {
2096 				str = str || str2;
2097 
2098 				if (str) {
2099 					str = decode(str);
2100 
2101 					// Force strings into single quote format
2102 					return "'" + str.replace(/\'/g, "\\'") + "'";
2103 				}
2104 
2105 				url = decode(url || url2 || url3);
2106 
2107 				// Convert the URL to relative/absolute depending on config
2108 				if (urlConverter)
2109 					url = urlConverter.call(urlConverterScope, url, 'style');
2110 
2111 				// Output new URL format
2112 				return "url('" + url.replace(/\'/g, "\\'") + "')";
2113 			};
2114 
2115 			if (css) {
2116 				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2117 				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2118 					return str.replace(/[;:]/g, encode);
2119 				});
2120 
2121 				// Parse styles
2122 				while (matches = styleRegExp.exec(css)) {
2123 					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2124 					value = matches[2].replace(trimRightRegExp, '');
2125 
2126 					if (name && value.length > 0) {
2127 						// Opera will produce 700 instead of bold in their style values
2128 						if (name === 'font-weight' && value === '700')
2129 							value = 'bold';
2130 						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2131 							value = value.toLowerCase();		
2132 
2133 						// Convert RGB colors to HEX
2134 						value = value.replace(rgbRegExp, toHex);
2135 
2136 						// Convert URLs and force them into url('value') format
2137 						value = value.replace(urlOrStrRegExp, processUrl);
2138 						styles[name] = isEncoded ? decode(value, true) : value;
2139 					}
2140 
2141 					styleRegExp.lastIndex = matches.index + matches[0].length;
2142 				}
2143 
2144 				// Compress the styles to reduce it's size for example IE will expand styles
2145 				compress("border", "");
2146 				compress("border", "-width");
2147 				compress("border", "-color");
2148 				compress("border", "-style");
2149 				compress("padding", "");
2150 				compress("margin", "");
2151 				compress2('border', 'border-width', 'border-style', 'border-color');
2152 
2153 				// Remove pointless border, IE produces these
2154 				if (styles.border === 'medium none')
2155 					delete styles.border;
2156 			}
2157 
2158 			return styles;
2159 		},
2160 
2161 		serialize : function(styles, element_name) {
2162 			var css = '', name, value;
2163 
2164 			function serializeStyles(name) {
2165 				var styleList, i, l, value;
2166 
2167 				styleList = schema.styles[name];
2168 				if (styleList) {
2169 					for (i = 0, l = styleList.length; i < l; i++) {
2170 						name = styleList[i];
2171 						value = styles[name];
2172 
2173 						if (value !== undef && value.length > 0)
2174 							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2175 					}
2176 				}
2177 			};
2178 
2179 			// Serialize styles according to schema
2180 			if (element_name && schema && schema.styles) {
2181 				// Serialize global styles and element specific styles
2182 				serializeStyles('*');
2183 				serializeStyles(element_name);
2184 			} else {
2185 				// Output the styles in the order they are inside the object
2186 				for (name in styles) {
2187 					value = styles[name];
2188 
2189 					if (value !== undef && value.length > 0)
2190 						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2191 				}
2192 			}
2193 
2194 			return css;
2195 		}
2196 	};
2197 };
2198 
2199 (function(tinymce) {
2200 	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2201 
2202 	function split(str, delim) {
2203 		return str.split(delim || ',');
2204 	};
2205 
2206 	function unpack(lookup, data) {
2207 		var key, elements = {};
2208 
2209 		function replace(value) {
2210 			return value.replace(/[A-Z]+/g, function(key) {
2211 				return replace(lookup[key]);
2212 			});
2213 		};
2214 
2215 		// Unpack lookup
2216 		for (key in lookup) {
2217 			if (lookup.hasOwnProperty(key))
2218 				lookup[key] = replace(lookup[key]);
2219 		}
2220 
2221 		// Unpack and parse data into object map
2222 		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2223 			attributes = split(attributes, '|');
2224 
2225 			elements[name] = {
2226 				attributes : makeMap(attributes),
2227 				attributesOrder : attributes,
2228 				children : makeMap(children, '|', {'#comment' : {}})
2229 			}
2230 		});
2231 
2232 		return elements;
2233 	};
2234 
2235 	function getHTML5() {
2236 		var html5 = mapCache.html5;
2237 
2238 		if (!html5) {
2239 			html5 = mapCache.html5 = unpack({
2240 					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2241 					B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2242 						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2243 					C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2244 						'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2245 						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2246 				}, 'html[A|manifest][body|head]' +
2247 					'head[A][base|command|link|meta|noscript|script|style|title]' +
2248 					'title[A][#]' +
2249 					'base[A|href|target][]' +
2250 					'link[A|href|rel|media|type|sizes][]' +
2251 					'meta[A|http-equiv|name|content|charset][]' +
2252 					'style[A|type|media|scoped][#]' +
2253 					'script[A|charset|type|src|defer|async][#]' +
2254 					'noscript[A][C]' +
2255 					'body[A][C]' +
2256 					'section[A][C]' +
2257 					'nav[A][C]' +
2258 					'article[A][C]' +
2259 					'aside[A][C]' +
2260 					'h1[A][B]' +
2261 					'h2[A][B]' +
2262 					'h3[A][B]' +
2263 					'h4[A][B]' +
2264 					'h5[A][B]' +
2265 					'h6[A][B]' +
2266 					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2267 					'header[A][C]' +
2268 					'footer[A][C]' +
2269 					'address[A][C]' +
2270 					'p[A][B]' +
2271 					'br[A][]' +
2272 					'pre[A][B]' +
2273 					'dialog[A][dd|dt]' +
2274 					'blockquote[A|cite][C]' +
2275 					'ol[A|start|reversed][li]' +
2276 					'ul[A][li]' +
2277 					'li[A|value][C]' +
2278 					'dl[A][dd|dt]' +
2279 					'dt[A][B]' +
2280 					'dd[A][C]' +
2281 					'a[A|href|target|ping|rel|media|type][B]' +
2282 					'em[A][B]' +
2283 					'strong[A][B]' +
2284 					'small[A][B]' +
2285 					'cite[A][B]' +
2286 					'q[A|cite][B]' +
2287 					'dfn[A][B]' +
2288 					'abbr[A][B]' +
2289 					'code[A][B]' +
2290 					'var[A][B]' +
2291 					'samp[A][B]' +
2292 					'kbd[A][B]' +
2293 					'sub[A][B]' +
2294 					'sup[A][B]' +
2295 					'i[A][B]' +
2296 					'b[A][B]' +
2297 					'mark[A][B]' +
2298 					'progress[A|value|max][B]' +
2299 					'meter[A|value|min|max|low|high|optimum][B]' +
2300 					'time[A|datetime][B]' +
2301 					'ruby[A][B|rt|rp]' +
2302 					'rt[A][B]' +
2303 					'rp[A][B]' +
2304 					'bdo[A][B]' +
2305 					'span[A][B]' +
2306 					'ins[A|cite|datetime][B]' +
2307 					'del[A|cite|datetime][B]' +
2308 					'figure[A][C|legend|figcaption]' +
2309 					'figcaption[A][C]' +
2310 					'img[A|alt|src|height|width|usemap|ismap][]' +
2311 					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2312 					'embed[A|src|height|width|type][]' +
2313 					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2314 					'param[A|name|value][]' +
2315 					'details[A|open][C|legend]' +
2316 					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2317 					'menu[A|type|label][C|li]' +
2318 					'legend[A][C|B]' +
2319 					'div[A][C]' +
2320 					'source[A|src|type|media][]' +
2321 					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2322 					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2323 					'hr[A][]' +
2324 					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2325 					'fieldset[A|disabled|form|name][C|legend]' +
2326 					'label[A|form|for][B]' +
2327 					'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2328 						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2329 					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2330 					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2331 					'datalist[A][B|option]' +
2332 					'optgroup[A|disabled|label][option]' +
2333 					'option[A|disabled|selected|label|value][]' +
2334 					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2335 					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2336 					'output[A|for|form|name][B]' +
2337 					'canvas[A|width|height][]' +
2338 					'map[A|name][B|C]' +
2339 					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2340 					'mathml[A][]' +
2341 					'svg[A][]' +
2342 					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2343 					'caption[A][C]' +
2344 					'colgroup[A|span][col]' +
2345 					'col[A|span][]' +
2346 					'thead[A][tr]' +
2347 					'tfoot[A][tr]' +
2348 					'tbody[A][tr]' +
2349 					'tr[A][th|td]' +
2350 					'th[A|headers|rowspan|colspan|scope][B]' +
2351 					'td[A|headers|rowspan|colspan][C]' +
2352 					'wbr[A][]'
2353 			);
2354 		}
2355 
2356 		return html5;
2357 	};
2358 
2359 	function getHTML4() {
2360 		var html4 = mapCache.html4;
2361 
2362 		if (!html4) {
2363 			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2364 			html4 = mapCache.html4 = unpack({
2365 				Z : 'H|K|N|O|P',
2366 				Y : 'X|form|R|Q',
2367 				ZG : 'E|span|width|align|char|charoff|valign',
2368 				X : 'p|T|div|U|W|isindex|fieldset|table',
2369 				ZF : 'E|align|char|charoff|valign',
2370 				W : 'pre|hr|blockquote|address|center|noframes',
2371 				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2372 				ZD : '[E][S]',
2373 				U : 'ul|ol|dl|menu|dir',
2374 				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2375 				T : 'h1|h2|h3|h4|h5|h6',
2376 				ZB : 'X|S|Q',
2377 				S : 'R|P',
2378 				ZA : 'a|G|J|M|O|P',
2379 				R : 'a|H|K|N|O',
2380 				Q : 'noscript|P',
2381 				P : 'ins|del|script',
2382 				O : 'input|select|textarea|label|button',
2383 				N : 'M|L',
2384 				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2385 				L : 'sub|sup',
2386 				K : 'J|I',
2387 				J : 'tt|i|b|u|s|strike',
2388 				I : 'big|small|font|basefont',
2389 				H : 'G|F',
2390 				G : 'br|span|bdo',
2391 				F : 'object|applet|img|map|iframe',
2392 				E : 'A|B|C',
2393 				D : 'accesskey|tabindex|onfocus|onblur',
2394 				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2395 				B : 'lang|xml:lang|dir',
2396 				A : 'id|class|style|title'
2397 			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2398 				'style[B|id|type|media|title|xml:space][]' + 
2399 				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2400 				'param[id|name|value|valuetype|type][]' + 
2401 				'p[E|align][#|S]' + 
2402 				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2403 				'br[A|clear][]' + 
2404 				'span[E][#|S]' + 
2405 				'bdo[A|C|B][#|S]' + 
2406 				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2407 				'h1[E|align][#|S]' + 
2408 				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2409 				'map[B|C|A|name][X|form|Q|area]' + 
2410 				'h2[E|align][#|S]' + 
2411 				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2412 				'h3[E|align][#|S]' + 
2413 				'tt[E][#|S]' + 
2414 				'i[E][#|S]' + 
2415 				'b[E][#|S]' + 
2416 				'u[E][#|S]' + 
2417 				's[E][#|S]' + 
2418 				'strike[E][#|S]' + 
2419 				'big[E][#|S]' + 
2420 				'small[E][#|S]' + 
2421 				'font[A|B|size|color|face][#|S]' + 
2422 				'basefont[id|size|color|face][]' + 
2423 				'em[E][#|S]' + 
2424 				'strong[E][#|S]' + 
2425 				'dfn[E][#|S]' + 
2426 				'code[E][#|S]' + 
2427 				'q[E|cite][#|S]' + 
2428 				'samp[E][#|S]' + 
2429 				'kbd[E][#|S]' + 
2430 				'var[E][#|S]' + 
2431 				'cite[E][#|S]' + 
2432 				'abbr[E][#|S]' + 
2433 				'acronym[E][#|S]' + 
2434 				'sub[E][#|S]' + 
2435 				'sup[E][#|S]' + 
2436 				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2437 				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2438 				'optgroup[E|disabled|label][option]' + 
2439 				'option[E|selected|disabled|label|value][]' + 
2440 				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2441 				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2442 				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2443 				'h4[E|align][#|S]' + 
2444 				'ins[E|cite|datetime][#|Y]' + 
2445 				'h5[E|align][#|S]' + 
2446 				'del[E|cite|datetime][#|Y]' + 
2447 				'h6[E|align][#|S]' + 
2448 				'div[E|align][#|Y]' + 
2449 				'ul[E|type|compact][li]' + 
2450 				'li[E|type|value][#|Y]' + 
2451 				'ol[E|type|compact|start][li]' + 
2452 				'dl[E|compact][dt|dd]' + 
2453 				'dt[E][#|S]' + 
2454 				'dd[E][#|Y]' + 
2455 				'menu[E|compact][li]' + 
2456 				'dir[E|compact][li]' + 
2457 				'pre[E|width|xml:space][#|ZA]' + 
2458 				'hr[E|align|noshade|size|width][]' + 
2459 				'blockquote[E|cite][#|Y]' + 
2460 				'address[E][#|S|p]' + 
2461 				'center[E][#|Y]' + 
2462 				'noframes[E][#|Y]' + 
2463 				'isindex[A|B|prompt][]' + 
2464 				'fieldset[E][#|legend|Y]' + 
2465 				'legend[E|accesskey|align][#|S]' + 
2466 				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2467 				'caption[E|align][#|S]' + 
2468 				'col[ZG][]' + 
2469 				'colgroup[ZG][col]' + 
2470 				'thead[ZF][tr]' + 
2471 				'tr[ZF|bgcolor][th|td]' + 
2472 				'th[E|ZE][#|Y]' + 
2473 				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2474 				'noscript[E][#|Y]' + 
2475 				'td[E|ZE][#|Y]' + 
2476 				'tfoot[ZF][tr]' + 
2477 				'tbody[ZF][tr]' + 
2478 				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2479 				'base[id|href|target][]' + 
2480 				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2481 			);
2482 		}
2483 
2484 		return html4;
2485 	};
2486 
2487 	tinymce.html.Schema = function(settings) {
2488 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2489 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2490 
2491 		// Creates an lookup table map object for the specified option or the default value
2492 		function createLookupTable(option, default_value, extend) {
2493 			var value = settings[option];
2494 
2495 			if (!value) {
2496 				// Get cached default map or make it if needed
2497 				value = mapCache[option];
2498 
2499 				if (!value) {
2500 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2501 					value = tinymce.extend(value, extend);
2502 
2503 					mapCache[option] = value;
2504 				}
2505 			} else {
2506 				// Create custom map
2507 				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2508 			}
2509 
2510 			return value;
2511 		};
2512 
2513 		settings = settings || {};
2514 		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2515 
2516 		// Allow all elements and attributes if verify_html is set to false
2517 		if (settings.verify_html === false)
2518 			settings.valid_elements = '*[*]';
2519 
2520 		// Build styles list
2521 		if (settings.valid_styles) {
2522 			validStyles = {};
2523 
2524 			// Convert styles into a rule list
2525 			each(settings.valid_styles, function(value, key) {
2526 				validStyles[key] = tinymce.explode(value);
2527 			});
2528 		}
2529 
2530 		// Setup map objects
2531 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2532 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2533 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2534 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2535 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2536 		blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 
2537 						'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 
2538 						'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
2539 
2540 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2541 		function patternToRegExp(str) {
2542 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2543 		};
2544 
2545 		// Parses the specified valid_elements string and adds to the current rules
2546 		// This function is a bit hard to read since it's heavily optimized for speed
2547 		function addValidElements(valid_elements) {
2548 			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2549 				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2550 				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2551 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2552 				hasPatternsRegExp = /[*?+]/;
2553 
2554 			if (valid_elements) {
2555 				// Split valid elements into an array with rules
2556 				valid_elements = split(valid_elements);
2557 
2558 				if (elements['@']) {
2559 					globalAttributes = elements['@'].attributes;
2560 					globalAttributesOrder = elements['@'].attributesOrder;
2561 				}
2562 
2563 				// Loop all rules
2564 				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2565 					// Parse element rule
2566 					matches = elementRuleRegExp.exec(valid_elements[ei]);
2567 					if (matches) {
2568 						// Setup local names for matches
2569 						prefix = matches[1];
2570 						elementName = matches[2];
2571 						outputName = matches[3];
2572 						attrData = matches[4];
2573 
2574 						// Create new attributes and attributesOrder
2575 						attributes = {};
2576 						attributesOrder = [];
2577 
2578 						// Create the new element
2579 						element = {
2580 							attributes : attributes,
2581 							attributesOrder : attributesOrder
2582 						};
2583 
2584 						// Padd empty elements prefix
2585 						if (prefix === '#')
2586 							element.paddEmpty = true;
2587 
2588 						// Remove empty elements prefix
2589 						if (prefix === '-')
2590 							element.removeEmpty = true;
2591 
2592 						// Copy attributes from global rule into current rule
2593 						if (globalAttributes) {
2594 							for (key in globalAttributes)
2595 								attributes[key] = globalAttributes[key];
2596 
2597 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2598 						}
2599 
2600 						// Attributes defined
2601 						if (attrData) {
2602 							attrData = split(attrData, '|');
2603 							for (ai = 0, al = attrData.length; ai < al; ai++) {
2604 								matches = attrRuleRegExp.exec(attrData[ai]);
2605 								if (matches) {
2606 									attr = {};
2607 									attrType = matches[1];
2608 									attrName = matches[2].replace(/::/g, ':');
2609 									prefix = matches[3];
2610 									value = matches[4];
2611 
2612 									// Required
2613 									if (attrType === '!') {
2614 										element.attributesRequired = element.attributesRequired || [];
2615 										element.attributesRequired.push(attrName);
2616 										attr.required = true;
2617 									}
2618 
2619 									// Denied from global
2620 									if (attrType === '-') {
2621 										delete attributes[attrName];
2622 										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2623 										continue;
2624 									}
2625 
2626 									// Default value
2627 									if (prefix) {
2628 										// Default value
2629 										if (prefix === '=') {
2630 											element.attributesDefault = element.attributesDefault || [];
2631 											element.attributesDefault.push({name: attrName, value: value});
2632 											attr.defaultValue = value;
2633 										}
2634 
2635 										// Forced value
2636 										if (prefix === ':') {
2637 											element.attributesForced = element.attributesForced || [];
2638 											element.attributesForced.push({name: attrName, value: value});
2639 											attr.forcedValue = value;
2640 										}
2641 
2642 										// Required values
2643 										if (prefix === '<')
2644 											attr.validValues = makeMap(value, '?');
2645 									}
2646 
2647 									// Check for attribute patterns
2648 									if (hasPatternsRegExp.test(attrName)) {
2649 										element.attributePatterns = element.attributePatterns || [];
2650 										attr.pattern = patternToRegExp(attrName);
2651 										element.attributePatterns.push(attr);
2652 									} else {
2653 										// Add attribute to order list if it doesn't already exist
2654 										if (!attributes[attrName])
2655 											attributesOrder.push(attrName);
2656 
2657 										attributes[attrName] = attr;
2658 									}
2659 								}
2660 							}
2661 						}
2662 
2663 						// Global rule, store away these for later usage
2664 						if (!globalAttributes && elementName == '@') {
2665 							globalAttributes = attributes;
2666 							globalAttributesOrder = attributesOrder;
2667 						}
2668 
2669 						// Handle substitute elements such as b/strong
2670 						if (outputName) {
2671 							element.outputName = elementName;
2672 							elements[outputName] = element;
2673 						}
2674 
2675 						// Add pattern or exact element
2676 						if (hasPatternsRegExp.test(elementName)) {
2677 							element.pattern = patternToRegExp(elementName);
2678 							patternElements.push(element);
2679 						} else
2680 							elements[elementName] = element;
2681 					}
2682 				}
2683 			}
2684 		};
2685 
2686 		function setValidElements(valid_elements) {
2687 			elements = {};
2688 			patternElements = [];
2689 
2690 			addValidElements(valid_elements);
2691 
2692 			each(schemaItems, function(element, name) {
2693 				children[name] = element.children;
2694 			});
2695 		};
2696 
2697 		// Adds custom non HTML elements to the schema
2698 		function addCustomElements(custom_elements) {
2699 			var customElementRegExp = /^(~)?(.+)$/;
2700 
2701 			if (custom_elements) {
2702 				each(split(custom_elements), function(rule) {
2703 					var matches = customElementRegExp.exec(rule),
2704 						inline = matches[1] === '~',
2705 						cloneName = inline ? 'span' : 'div',
2706 						name = matches[2];
2707 
2708 					children[name] = children[cloneName];
2709 					customElementsMap[name] = cloneName;
2710 
2711 					// If it's not marked as inline then add it to valid block elements
2712 					if (!inline)
2713 						blockElementsMap[name] = {};
2714 
2715 					// Add custom elements at span/div positions
2716 					each(children, function(element, child) {
2717 						if (element[cloneName])
2718 							element[name] = element[cloneName];
2719 					});
2720 				});
2721 			}
2722 		};
2723 
2724 		// Adds valid children to the schema object
2725 		function addValidChildren(valid_children) {
2726 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2727 
2728 			if (valid_children) {
2729 				each(split(valid_children), function(rule) {
2730 					var matches = childRuleRegExp.exec(rule), parent, prefix;
2731 
2732 					if (matches) {
2733 						prefix = matches[1];
2734 
2735 						// Add/remove items from default
2736 						if (prefix)
2737 							parent = children[matches[2]];
2738 						else
2739 							parent = children[matches[2]] = {'#comment' : {}};
2740 
2741 						parent = children[matches[2]];
2742 
2743 						each(split(matches[3], '|'), function(child) {
2744 							if (prefix === '-')
2745 								delete parent[child];
2746 							else
2747 								parent[child] = {};
2748 						});
2749 					}
2750 				});
2751 			}
2752 		};
2753 
2754 		function getElementRule(name) {
2755 			var element = elements[name], i;
2756 
2757 			// Exact match found
2758 			if (element)
2759 				return element;
2760 
2761 			// No exact match then try the patterns
2762 			i = patternElements.length;
2763 			while (i--) {
2764 				element = patternElements[i];
2765 
2766 				if (element.pattern.test(name))
2767 					return element;
2768 			}
2769 		};
2770 
2771 		if (!settings.valid_elements) {
2772 			// No valid elements defined then clone the elements from the schema spec
2773 			each(schemaItems, function(element, name) {
2774 				elements[name] = {
2775 					attributes : element.attributes,
2776 					attributesOrder : element.attributesOrder
2777 				};
2778 
2779 				children[name] = element.children;
2780 			});
2781 
2782 			// Switch these on HTML4
2783 			if (settings.schema != "html5") {
2784 				each(split('strong/b,em/i'), function(item) {
2785 					item = split(item, '/');
2786 					elements[item[1]].outputName = item[0];
2787 				});
2788 			}
2789 
2790 			// Add default alt attribute for images
2791 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
2792 
2793 			// Remove these if they are empty by default
2794 			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
2795 				if (elements[name]) {
2796 					elements[name].removeEmpty = true;
2797 				}
2798 			});
2799 
2800 			// Padd these by default
2801 			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2802 				elements[name].paddEmpty = true;
2803 			});
2804 		} else
2805 			setValidElements(settings.valid_elements);
2806 
2807 		addCustomElements(settings.custom_elements);
2808 		addValidChildren(settings.valid_children);
2809 		addValidElements(settings.extended_valid_elements);
2810 
2811 		// Todo: Remove this when we fix list handling to be valid
2812 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2813 
2814 		// Delete invalid elements
2815 		if (settings.invalid_elements) {
2816 			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2817 				if (elements[item])
2818 					delete elements[item];
2819 			});
2820 		}
2821 
2822 		// If the user didn't allow span only allow internal spans
2823 		if (!getElementRule('span'))
2824 			addValidElements('span[!data-mce-type|*]');
2825 
2826 		self.children = children;
2827 
2828 		self.styles = validStyles;
2829 
2830 		self.getBoolAttrs = function() {
2831 			return boolAttrMap;
2832 		};
2833 
2834 		self.getBlockElements = function() {
2835 			return blockElementsMap;
2836 		};
2837 
2838 		self.getShortEndedElements = function() {
2839 			return shortEndedElementsMap;
2840 		};
2841 
2842 		self.getSelfClosingElements = function() {
2843 			return selfClosingElementsMap;
2844 		};
2845 
2846 		self.getNonEmptyElements = function() {
2847 			return nonEmptyElementsMap;
2848 		};
2849 
2850 		self.getWhiteSpaceElements = function() {
2851 			return whiteSpaceElementsMap;
2852 		};
2853 
2854 		self.isValidChild = function(name, child) {
2855 			var parent = children[name];
2856 
2857 			return !!(parent && parent[child]);
2858 		};
2859 
2860 		self.isValid = function(name, attr) {
2861 			var attrPatterns, i, rule = getElementRule(name);
2862 
2863 			// Check if it's a valid element
2864 			if (rule) {
2865 				if (attr) {
2866 					// Check if attribute name exists
2867 					if (rule.attributes[attr]) {
2868 						return true;
2869 					}
2870 
2871 					// Check if attribute matches a regexp pattern
2872 					attrPatterns = rule.attributePatterns;
2873 					if (attrPatterns) {
2874 						i = attrPatterns.length;
2875 						while (i--) {
2876 							if (attrPatterns[i].pattern.test(name)) {
2877 								return true;
2878 							}
2879 						}
2880 					}
2881 				} else {
2882 					return true;
2883 				}
2884 			}
2885 
2886 			// No match
2887 			return false;
2888 		};
2889 		
2890 		self.getElementRule = getElementRule;
2891 
2892 		self.getCustomElements = function() {
2893 			return customElementsMap;
2894 		};
2895 
2896 		self.addValidElements = addValidElements;
2897 
2898 		self.setValidElements = setValidElements;
2899 
2900 		self.addCustomElements = addCustomElements;
2901 
2902 		self.addValidChildren = addValidChildren;
2903 	};
2904 })(tinymce);
2905 
2906 (function(tinymce) {
2907 	tinymce.html.SaxParser = function(settings, schema) {
2908 		var self = this, noop = function() {};
2909 
2910 		settings = settings || {};
2911 		self.schema = schema = schema || new tinymce.html.Schema();
2912 
2913 		if (settings.fix_self_closing !== false)
2914 			settings.fix_self_closing = true;
2915 
2916 		// Add handler functions from settings and setup default handlers
2917 		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2918 			if (name)
2919 				self[name] = settings[name] || noop;
2920 		});
2921 
2922 		self.parse = function(html) {
2923 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2924 				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
2925 				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2926 				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
2927 
2928 			function processEndTag(name) {
2929 				var pos, i;
2930 
2931 				// Find position of parent of the same type
2932 				pos = stack.length;
2933 				while (pos--) {
2934 					if (stack[pos].name === name)
2935 						break;						
2936 				}
2937 
2938 				// Found parent
2939 				if (pos >= 0) {
2940 					// Close all the open elements
2941 					for (i = stack.length - 1; i >= pos; i--) {
2942 						name = stack[i];
2943 
2944 						if (name.valid)
2945 							self.end(name.name);
2946 					}
2947 
2948 					// Remove the open elements from the stack
2949 					stack.length = pos;
2950 				}
2951 			};
2952 
2953 			function parseAttribute(match, name, value, val2, val3) {
2954 				var attrRule, i;
2955 
2956 				name = name.toLowerCase();
2957 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2958 
2959 				// Validate name and value
2960 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2961 					attrRule = validAttributesMap[name];
2962 
2963 					// Find rule by pattern matching
2964 					if (!attrRule && validAttributePatterns) {
2965 						i = validAttributePatterns.length;
2966 						while (i--) {
2967 							attrRule = validAttributePatterns[i];
2968 							if (attrRule.pattern.test(name))
2969 								break;
2970 						}
2971 
2972 						// No rule matched
2973 						if (i === -1)
2974 							attrRule = null;
2975 					}
2976 
2977 					// No attribute rule found
2978 					if (!attrRule)
2979 						return;
2980 
2981 					// Validate value
2982 					if (attrRule.validValues && !(value in attrRule.validValues))
2983 						return;
2984 				}
2985 
2986 				// Add attribute to list and map
2987 				attrList.map[name] = value;
2988 				attrList.push({
2989 					name: name,
2990 					value: value
2991 				});
2992 			};
2993 
2994 			// Precompile RegExps and map objects
2995 			tokenRegExp = new RegExp('<(?:' +
2996 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
2997 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2998 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2999 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
3000 				'(?:\\/([^>]+)>)|' + // End element
3001 				'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
3002 			')', 'g');
3003 
3004 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
3005 			specialElements = {
3006 				'script' : /<\/script[^>]*>/gi,
3007 				'style' : /<\/style[^>]*>/gi,
3008 				'noscript' : /<\/noscript[^>]*>/gi
3009 			};
3010 
3011 			// Setup lookup tables for empty elements and boolean attributes
3012 			shortEndedElements = schema.getShortEndedElements();
3013 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
3014 			fillAttrsMap = schema.getBoolAttrs();
3015 			validate = settings.validate;
3016 			removeInternalElements = settings.remove_internals;
3017 			fixSelfClosing = settings.fix_self_closing;
3018 			isIE = tinymce.isIE;
3019 			invalidPrefixRegExp = /^:/;
3020 
3021 			while (matches = tokenRegExp.exec(html)) {
3022 				// Text
3023 				if (index < matches.index)
3024 					self.text(decode(html.substr(index, matches.index - index)));
3025 
3026 				if (value = matches[6]) { // End element
3027 					value = value.toLowerCase();
3028 
3029 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3030 					if (isIE && invalidPrefixRegExp.test(value))
3031 						value = value.substr(1);
3032 
3033 					processEndTag(value);
3034 				} else if (value = matches[7]) { // Start element
3035 					value = value.toLowerCase();
3036 
3037 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3038 					if (isIE && invalidPrefixRegExp.test(value))
3039 						value = value.substr(1);
3040 
3041 					isShortEnded = value in shortEndedElements;
3042 
3043 					// Is self closing tag for example an <li> after an open <li>
3044 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3045 						processEndTag(value);
3046 
3047 					// Validate element
3048 					if (!validate || (elementRule = schema.getElementRule(value))) {
3049 						isValidElement = true;
3050 
3051 						// Grab attributes map and patters when validation is enabled
3052 						if (validate) {
3053 							validAttributesMap = elementRule.attributes;
3054 							validAttributePatterns = elementRule.attributePatterns;
3055 						}
3056 
3057 						// Parse attributes
3058 						if (attribsValue = matches[8]) {
3059 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3060 
3061 							// If the element has internal attributes then remove it if we are told to do so
3062 							if (isInternalElement && removeInternalElements)
3063 								isValidElement = false;
3064 
3065 							attrList = [];
3066 							attrList.map = {};
3067 
3068 							attribsValue.replace(attrRegExp, parseAttribute);
3069 						} else {
3070 							attrList = [];
3071 							attrList.map = {};
3072 						}
3073 
3074 						// Process attributes if validation is enabled
3075 						if (validate && !isInternalElement) {
3076 							attributesRequired = elementRule.attributesRequired;
3077 							attributesDefault = elementRule.attributesDefault;
3078 							attributesForced = elementRule.attributesForced;
3079 
3080 							// Handle forced attributes
3081 							if (attributesForced) {
3082 								i = attributesForced.length;
3083 								while (i--) {
3084 									attr = attributesForced[i];
3085 									name = attr.name;
3086 									attrValue = attr.value;
3087 
3088 									if (attrValue === '{$uid}')
3089 										attrValue = 'mce_' + idCount++;
3090 
3091 									attrList.map[name] = attrValue;
3092 									attrList.push({name: name, value: attrValue});
3093 								}
3094 							}
3095 
3096 							// Handle default attributes
3097 							if (attributesDefault) {
3098 								i = attributesDefault.length;
3099 								while (i--) {
3100 									attr = attributesDefault[i];
3101 									name = attr.name;
3102 
3103 									if (!(name in attrList.map)) {
3104 										attrValue = attr.value;
3105 
3106 										if (attrValue === '{$uid}')
3107 											attrValue = 'mce_' + idCount++;
3108 
3109 										attrList.map[name] = attrValue;
3110 										attrList.push({name: name, value: attrValue});
3111 									}
3112 								}
3113 							}
3114 
3115 							// Handle required attributes
3116 							if (attributesRequired) {
3117 								i = attributesRequired.length;
3118 								while (i--) {
3119 									if (attributesRequired[i] in attrList.map)
3120 										break;
3121 								}
3122 
3123 								// None of the required attributes where found
3124 								if (i === -1)
3125 									isValidElement = false;
3126 							}
3127 
3128 							// Invalidate element if it's marked as bogus
3129 							if (attrList.map['data-mce-bogus'])
3130 								isValidElement = false;
3131 						}
3132 
3133 						if (isValidElement)
3134 							self.start(value, attrList, isShortEnded);
3135 					} else
3136 						isValidElement = false;
3137 
3138 					// Treat script, noscript and style a bit different since they may include code that looks like elements
3139 					if (endRegExp = specialElements[value]) {
3140 						endRegExp.lastIndex = index = matches.index + matches[0].length;
3141 
3142 						if (matches = endRegExp.exec(html)) {
3143 							if (isValidElement)
3144 								text = html.substr(index, matches.index - index);
3145 
3146 							index = matches.index + matches[0].length;
3147 						} else {
3148 							text = html.substr(index);
3149 							index = html.length;
3150 						}
3151 
3152 						if (isValidElement && text.length > 0)
3153 							self.text(text, true);
3154 
3155 						if (isValidElement)
3156 							self.end(value);
3157 
3158 						tokenRegExp.lastIndex = index;
3159 						continue;
3160 					}
3161 
3162 					// Push value on to stack
3163 					if (!isShortEnded) {
3164 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3165 							stack.push({name: value, valid: isValidElement});
3166 						else if (isValidElement)
3167 							self.end(value);
3168 					}
3169 				} else if (value = matches[1]) { // Comment
3170 					self.comment(value);
3171 				} else if (value = matches[2]) { // CDATA
3172 					self.cdata(value);
3173 				} else if (value = matches[3]) { // DOCTYPE
3174 					self.doctype(value);
3175 				} else if (value = matches[4]) { // PI
3176 					self.pi(value, matches[5]);
3177 				}
3178 
3179 				index = matches.index + matches[0].length;
3180 			}
3181 
3182 			// Text
3183 			if (index < html.length)
3184 				self.text(decode(html.substr(index)));
3185 
3186 			// Close any open elements
3187 			for (i = stack.length - 1; i >= 0; i--) {
3188 				value = stack[i];
3189 
3190 				if (value.valid)
3191 					self.end(value.name);
3192 			}
3193 		};
3194 	}
3195 })(tinymce);
3196 
3197 (function(tinymce) {
3198 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3199 		'#text' : 3,
3200 		'#comment' : 8,
3201 		'#cdata' : 4,
3202 		'#pi' : 7,
3203 		'#doctype' : 10,
3204 		'#document-fragment' : 11
3205 	};
3206 
3207 	// Walks the tree left/right
3208 	function walk(node, root_node, prev) {
3209 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3210 
3211 		// Walk into nodes if it has a start
3212 		if (node[startName])
3213 			return node[startName];
3214 
3215 		// Return the sibling if it has one
3216 		if (node !== root_node) {
3217 			sibling = node[siblingName];
3218 
3219 			if (sibling)
3220 				return sibling;
3221 
3222 			// Walk up the parents to look for siblings
3223 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3224 				sibling = parent[siblingName];
3225 
3226 				if (sibling)
3227 					return sibling;
3228 			}
3229 		}
3230 	};
3231 
3232 	function Node(name, type) {
3233 		this.name = name;
3234 		this.type = type;
3235 
3236 		if (type === 1) {
3237 			this.attributes = [];
3238 			this.attributes.map = {};
3239 		}
3240 	}
3241 
3242 	tinymce.extend(Node.prototype, {
3243 		replace : function(node) {
3244 			var self = this;
3245 
3246 			if (node.parent)
3247 				node.remove();
3248 
3249 			self.insert(node, self);
3250 			self.remove();
3251 
3252 			return self;
3253 		},
3254 
3255 		attr : function(name, value) {
3256 			var self = this, attrs, i, undef;
3257 
3258 			if (typeof name !== "string") {
3259 				for (i in name)
3260 					self.attr(i, name[i]);
3261 
3262 				return self;
3263 			}
3264 
3265 			if (attrs = self.attributes) {
3266 				if (value !== undef) {
3267 					// Remove attribute
3268 					if (value === null) {
3269 						if (name in attrs.map) {
3270 							delete attrs.map[name];
3271 
3272 							i = attrs.length;
3273 							while (i--) {
3274 								if (attrs[i].name === name) {
3275 									attrs = attrs.splice(i, 1);
3276 									return self;
3277 								}
3278 							}
3279 						}
3280 
3281 						return self;
3282 					}
3283 
3284 					// Set attribute
3285 					if (name in attrs.map) {
3286 						// Set attribute
3287 						i = attrs.length;
3288 						while (i--) {
3289 							if (attrs[i].name === name) {
3290 								attrs[i].value = value;
3291 								break;
3292 							}
3293 						}
3294 					} else
3295 						attrs.push({name: name, value: value});
3296 
3297 					attrs.map[name] = value;
3298 
3299 					return self;
3300 				} else {
3301 					return attrs.map[name];
3302 				}
3303 			}
3304 		},
3305 
3306 		clone : function() {
3307 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3308 
3309 			// Clone element attributes
3310 			if (selfAttrs = self.attributes) {
3311 				cloneAttrs = [];
3312 				cloneAttrs.map = {};
3313 
3314 				for (i = 0, l = selfAttrs.length; i < l; i++) {
3315 					selfAttr = selfAttrs[i];
3316 
3317 					// Clone everything except id
3318 					if (selfAttr.name !== 'id') {
3319 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3320 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3321 					}
3322 				}
3323 
3324 				clone.attributes = cloneAttrs;
3325 			}
3326 
3327 			clone.value = self.value;
3328 			clone.shortEnded = self.shortEnded;
3329 
3330 			return clone;
3331 		},
3332 
3333 		wrap : function(wrapper) {
3334 			var self = this;
3335 
3336 			self.parent.insert(wrapper, self);
3337 			wrapper.append(self);
3338 
3339 			return self;
3340 		},
3341 
3342 		unwrap : function() {
3343 			var self = this, node, next;
3344 
3345 			for (node = self.firstChild; node; ) {
3346 				next = node.next;
3347 				self.insert(node, self, true);
3348 				node = next;
3349 			}
3350 
3351 			self.remove();
3352 		},
3353 
3354 		remove : function() {
3355 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3356 
3357 			if (parent) {
3358 				if (parent.firstChild === self) {
3359 					parent.firstChild = next;
3360 
3361 					if (next)
3362 						next.prev = null;
3363 				} else {
3364 					prev.next = next;
3365 				}
3366 
3367 				if (parent.lastChild === self) {
3368 					parent.lastChild = prev;
3369 
3370 					if (prev)
3371 						prev.next = null;
3372 				} else {
3373 					next.prev = prev;
3374 				}
3375 
3376 				self.parent = self.next = self.prev = null;
3377 			}
3378 
3379 			return self;
3380 		},
3381 
3382 		append : function(node) {
3383 			var self = this, last;
3384 
3385 			if (node.parent)
3386 				node.remove();
3387 
3388 			last = self.lastChild;
3389 			if (last) {
3390 				last.next = node;
3391 				node.prev = last;
3392 				self.lastChild = node;
3393 			} else
3394 				self.lastChild = self.firstChild = node;
3395 
3396 			node.parent = self;
3397 
3398 			return node;
3399 		},
3400 
3401 		insert : function(node, ref_node, before) {
3402 			var parent;
3403 
3404 			if (node.parent)
3405 				node.remove();
3406 
3407 			parent = ref_node.parent || this;
3408 
3409 			if (before) {
3410 				if (ref_node === parent.firstChild)
3411 					parent.firstChild = node;
3412 				else
3413 					ref_node.prev.next = node;
3414 
3415 				node.prev = ref_node.prev;
3416 				node.next = ref_node;
3417 				ref_node.prev = node;
3418 			} else {
3419 				if (ref_node === parent.lastChild)
3420 					parent.lastChild = node;
3421 				else
3422 					ref_node.next.prev = node;
3423 
3424 				node.next = ref_node.next;
3425 				node.prev = ref_node;
3426 				ref_node.next = node;
3427 			}
3428 
3429 			node.parent = parent;
3430 
3431 			return node;
3432 		},
3433 
3434 		getAll : function(name) {
3435 			var self = this, node, collection = [];
3436 
3437 			for (node = self.firstChild; node; node = walk(node, self)) {
3438 				if (node.name === name)
3439 					collection.push(node);
3440 			}
3441 
3442 			return collection;
3443 		},
3444 
3445 		empty : function() {
3446 			var self = this, nodes, i, node;
3447 
3448 			// Remove all children
3449 			if (self.firstChild) {
3450 				nodes = [];
3451 
3452 				// Collect the children
3453 				for (node = self.firstChild; node; node = walk(node, self))
3454 					nodes.push(node);
3455 
3456 				// Remove the children
3457 				i = nodes.length;
3458 				while (i--) {
3459 					node = nodes[i];
3460 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3461 				}
3462 			}
3463 
3464 			self.firstChild = self.lastChild = null;
3465 
3466 			return self;
3467 		},
3468 
3469 		isEmpty : function(elements) {
3470 			var self = this, node = self.firstChild, i, name;
3471 
3472 			if (node) {
3473 				do {
3474 					if (node.type === 1) {
3475 						// Ignore bogus elements
3476 						if (node.attributes.map['data-mce-bogus'])
3477 							continue;
3478 
3479 						// Keep empty elements like <img />
3480 						if (elements[node.name])
3481 							return false;
3482 
3483 						// Keep elements with data attributes or name attribute like <a name="1"></a>
3484 						i = node.attributes.length;
3485 						while (i--) {
3486 							name = node.attributes[i].name;
3487 							if (name === "name" || name.indexOf('data-') === 0)
3488 								return false;
3489 						}
3490 					}
3491 
3492 					// Keep comments
3493 					if (node.type === 8)
3494 						return false;
3495 					
3496 					// Keep non whitespace text nodes
3497 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3498 						return false;
3499 				} while (node = walk(node, self));
3500 			}
3501 
3502 			return true;
3503 		},
3504 
3505 		walk : function(prev) {
3506 			return walk(this, null, prev);
3507 		}
3508 	});
3509 
3510 	tinymce.extend(Node, {
3511 		create : function(name, attrs) {
3512 			var node, attrName;
3513 
3514 			// Create node
3515 			node = new Node(name, typeLookup[name] || 1);
3516 
3517 			// Add attributes if needed
3518 			if (attrs) {
3519 				for (attrName in attrs)
3520 					node.attr(attrName, attrs[attrName]);
3521 			}
3522 
3523 			return node;
3524 		}
3525 	});
3526 
3527 	tinymce.html.Node = Node;
3528 })(tinymce);
3529 
3530 (function(tinymce) {
3531 	var Node = tinymce.html.Node;
3532 
3533 	tinymce.html.DomParser = function(settings, schema) {
3534 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3535 
3536 		settings = settings || {};
3537 		settings.validate = "validate" in settings ? settings.validate : true;
3538 		settings.root_name = settings.root_name || 'body';
3539 		self.schema = schema = schema || new tinymce.html.Schema();
3540 
3541 		function fixInvalidChildren(nodes) {
3542 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3543 				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3544 
3545 			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3546 			nonEmptyElements = schema.getNonEmptyElements();
3547 
3548 			for (ni = 0; ni < nodes.length; ni++) {
3549 				node = nodes[ni];
3550 
3551 				// Already removed
3552 				if (!node.parent)
3553 					continue;
3554 
3555 				// Get list of all parent nodes until we find a valid parent to stick the child into
3556 				parents = [node];
3557 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3558 					parents.push(parent);
3559 
3560 				// Found a suitable parent
3561 				if (parent && parents.length > 1) {
3562 					// Reverse the array since it makes looping easier
3563 					parents.reverse();
3564 
3565 					// Clone the related parent and insert that after the moved node
3566 					newParent = currentNode = self.filterNode(parents[0].clone());
3567 
3568 					// Start cloning and moving children on the left side of the target node
3569 					for (i = 0; i < parents.length - 1; i++) {
3570 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3571 							tempNode = self.filterNode(parents[i].clone());
3572 							currentNode.append(tempNode);
3573 						} else
3574 							tempNode = currentNode;
3575 
3576 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3577 							nextNode = childNode.next;
3578 							tempNode.append(childNode);
3579 							childNode = nextNode;
3580 						}
3581 
3582 						currentNode = tempNode;
3583 					}
3584 
3585 					if (!newParent.isEmpty(nonEmptyElements)) {
3586 						parent.insert(newParent, parents[0], true);
3587 						parent.insert(node, newParent);
3588 					} else {
3589 						parent.insert(node, parents[0], true);
3590 					}
3591 
3592 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3593 					parent = parents[0];
3594 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3595 						parent.empty().remove();
3596 					}
3597 				} else if (node.parent) {
3598 					// If it's an LI try to find a UL/OL for it or wrap it
3599 					if (node.name === 'li') {
3600 						sibling = node.prev;
3601 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3602 							sibling.append(node);
3603 							continue;
3604 						}
3605 
3606 						sibling = node.next;
3607 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3608 							sibling.insert(node, sibling.firstChild, true);
3609 							continue;
3610 						}
3611 
3612 						node.wrap(self.filterNode(new Node('ul', 1)));
3613 						continue;
3614 					}
3615 
3616 					// Try wrapping the element in a DIV
3617 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3618 						node.wrap(self.filterNode(new Node('div', 1)));
3619 					} else {
3620 						// We failed wrapping it, then remove or unwrap it
3621 						if (node.name === 'style' || node.name === 'script')
3622 							node.empty().remove();
3623 						else
3624 							node.unwrap();
3625 					}
3626 				}
3627 			}
3628 		};
3629 
3630 		self.filterNode = function(node) {
3631 			var i, name, list;
3632 
3633 			// Run element filters
3634 			if (name in nodeFilters) {
3635 				list = matchedNodes[name];
3636 
3637 				if (list)
3638 					list.push(node);
3639 				else
3640 					matchedNodes[name] = [node];
3641 			}
3642 
3643 			// Run attribute filters
3644 			i = attributeFilters.length;
3645 			while (i--) {
3646 				name = attributeFilters[i].name;
3647 
3648 				if (name in node.attributes.map) {
3649 					list = matchedAttributes[name];
3650 
3651 					if (list)
3652 						list.push(node);
3653 					else
3654 						matchedAttributes[name] = [node];
3655 				}
3656 			}
3657 
3658 			return node;
3659 		};
3660 
3661 		self.addNodeFilter = function(name, callback) {
3662 			tinymce.each(tinymce.explode(name), function(name) {
3663 				var list = nodeFilters[name];
3664 
3665 				if (!list)
3666 					nodeFilters[name] = list = [];
3667 
3668 				list.push(callback);
3669 			});
3670 		};
3671 
3672 		self.addAttributeFilter = function(name, callback) {
3673 			tinymce.each(tinymce.explode(name), function(name) {
3674 				var i;
3675 
3676 				for (i = 0; i < attributeFilters.length; i++) {
3677 					if (attributeFilters[i].name === name) {
3678 						attributeFilters[i].callbacks.push(callback);
3679 						return;
3680 					}
3681 				}
3682 
3683 				attributeFilters.push({name: name, callbacks: [callback]});
3684 			});
3685 		};
3686 
3687 		self.parse = function(html, args) {
3688 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3689 				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3690 				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3691 
3692 			args = args || {};
3693 			matchedNodes = {};
3694 			matchedAttributes = {};
3695 			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3696 			nonEmptyElements = schema.getNonEmptyElements();
3697 			children = schema.children;
3698 			validate = settings.validate;
3699 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3700 
3701 			whiteSpaceElements = schema.getWhiteSpaceElements();
3702 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3703 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3704 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3705 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3706 
3707 			function addRootBlocks() {
3708 				var node = rootNode.firstChild, next, rootBlockNode;
3709 
3710 				while (node) {
3711 					next = node.next;
3712 
3713 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3714 						if (!rootBlockNode) {
3715 							// Create a new root block element
3716 							rootBlockNode = createNode(rootBlockName, 1);
3717 							rootNode.insert(rootBlockNode, node);
3718 							rootBlockNode.append(node);
3719 						} else
3720 							rootBlockNode.append(node);
3721 					} else {
3722 						rootBlockNode = null;
3723 					}
3724 
3725 					node = next;
3726 				};
3727 			};
3728 
3729 			function createNode(name, type) {
3730 				var node = new Node(name, type), list;
3731 
3732 				if (name in nodeFilters) {
3733 					list = matchedNodes[name];
3734 
3735 					if (list)
3736 						list.push(node);
3737 					else
3738 						matchedNodes[name] = [node];
3739 				}
3740 
3741 				return node;
3742 			};
3743 
3744 			function removeWhitespaceBefore(node) {
3745 				var textNode, textVal, sibling;
3746 
3747 				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3748 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3749 
3750 					if (textVal.length > 0) {
3751 						textNode.value = textVal;
3752 						textNode = textNode.prev;
3753 					} else {
3754 						sibling = textNode.prev;
3755 						textNode.remove();
3756 						textNode = sibling;
3757 					}
3758 				}
3759 			};
3760 
3761 			function cloneAndExcludeBlocks(input) {
3762 				var name, output = {};
3763 
3764 				for (name in input) {
3765 					if (name !== 'li' && name != 'p') {
3766 						output[name] = input[name];
3767 					}
3768 				}
3769 
3770 				return output;
3771 			};
3772 
3773 			parser = new tinymce.html.SaxParser({
3774 				validate : validate,
3775 
3776 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
3777 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
3778 
3779 				cdata: function(text) {
3780 					node.append(createNode('#cdata', 4)).value = text;
3781 				},
3782 
3783 				text: function(text, raw) {
3784 					var textNode;
3785 
3786 					// Trim all redundant whitespace on non white space elements
3787 					if (!isInWhiteSpacePreservedElement) {
3788 						text = text.replace(allWhiteSpaceRegExp, ' ');
3789 
3790 						if (node.lastChild && blockElements[node.lastChild.name])
3791 							text = text.replace(startWhiteSpaceRegExp, '');
3792 					}
3793 
3794 					// Do we need to create the node
3795 					if (text.length !== 0) {
3796 						textNode = createNode('#text', 3);
3797 						textNode.raw = !!raw;
3798 						node.append(textNode).value = text;
3799 					}
3800 				},
3801 
3802 				comment: function(text) {
3803 					node.append(createNode('#comment', 8)).value = text;
3804 				},
3805 
3806 				pi: function(name, text) {
3807 					node.append(createNode(name, 7)).value = text;
3808 					removeWhitespaceBefore(node);
3809 				},
3810 
3811 				doctype: function(text) {
3812 					var newNode;
3813 		
3814 					newNode = node.append(createNode('#doctype', 10));
3815 					newNode.value = text;
3816 					removeWhitespaceBefore(node);
3817 				},
3818 
3819 				start: function(name, attrs, empty) {
3820 					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3821 
3822 					elementRule = validate ? schema.getElementRule(name) : {};
3823 					if (elementRule) {
3824 						newNode = createNode(elementRule.outputName || name, 1);
3825 						newNode.attributes = attrs;
3826 						newNode.shortEnded = empty;
3827 
3828 						node.append(newNode);
3829 
3830 						// Check if node is valid child of the parent node is the child is
3831 						// unknown we don't collect it since it's probably a custom element
3832 						parent = children[node.name];
3833 						if (parent && children[newNode.name] && !parent[newNode.name])
3834 							invalidChildren.push(newNode);
3835 
3836 						attrFiltersLen = attributeFilters.length;
3837 						while (attrFiltersLen--) {
3838 							attrName = attributeFilters[attrFiltersLen].name;
3839 
3840 							if (attrName in attrs.map) {
3841 								list = matchedAttributes[attrName];
3842 
3843 								if (list)
3844 									list.push(newNode);
3845 								else
3846 									matchedAttributes[attrName] = [newNode];
3847 							}
3848 						}
3849 
3850 						// Trim whitespace before block
3851 						if (blockElements[name])
3852 							removeWhitespaceBefore(newNode);
3853 
3854 						// Change current node if the element wasn't empty i.e not <br /> or <img />
3855 						if (!empty)
3856 							node = newNode;
3857 
3858 						// Check if we are inside a whitespace preserved element
3859 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3860 							isInWhiteSpacePreservedElement = true;
3861 						}
3862 					}
3863 				},
3864 
3865 				end: function(name) {
3866 					var textNode, elementRule, text, sibling, tempNode;
3867 
3868 					elementRule = validate ? schema.getElementRule(name) : {};
3869 					if (elementRule) {
3870 						if (blockElements[name]) {
3871 							if (!isInWhiteSpacePreservedElement) {
3872 								// Trim whitespace of the first node in a block
3873 								textNode = node.firstChild;
3874 								if (textNode && textNode.type === 3) {
3875 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
3876 
3877 									// Any characters left after trim or should we remove it
3878 									if (text.length > 0) {
3879 										textNode.value = text;
3880 										textNode = textNode.next;
3881 									} else {
3882 										sibling = textNode.next;
3883 										textNode.remove();
3884 										textNode = sibling;
3885 									}
3886 
3887 									// Remove any pure whitespace siblings
3888 									while (textNode && textNode.type === 3) {
3889 										text = textNode.value;
3890 										sibling = textNode.next;
3891 
3892 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3893 											textNode.remove();
3894 											textNode = sibling;
3895 										}
3896 
3897 										textNode = sibling;
3898 									}
3899 								}
3900 
3901 								// Trim whitespace of the last node in a block
3902 								textNode = node.lastChild;
3903 								if (textNode && textNode.type === 3) {
3904 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
3905 
3906 									// Any characters left after trim or should we remove it
3907 									if (text.length > 0) {
3908 										textNode.value = text;
3909 										textNode = textNode.prev;
3910 									} else {
3911 										sibling = textNode.prev;
3912 										textNode.remove();
3913 										textNode = sibling;
3914 									}
3915 
3916 									// Remove any pure whitespace siblings
3917 									while (textNode && textNode.type === 3) {
3918 										text = textNode.value;
3919 										sibling = textNode.prev;
3920 
3921 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3922 											textNode.remove();
3923 											textNode = sibling;
3924 										}
3925 
3926 										textNode = sibling;
3927 									}
3928 								}
3929 							}
3930 
3931 							// Trim start white space
3932 							textNode = node.prev;
3933 							if (textNode && textNode.type === 3) {
3934 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
3935 
3936 								if (text.length > 0)
3937 									textNode.value = text;
3938 								else
3939 									textNode.remove();
3940 							}
3941 						}
3942 
3943 						// Check if we exited a whitespace preserved element
3944 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3945 							isInWhiteSpacePreservedElement = false;
3946 						}
3947 
3948 						// Handle empty nodes
3949 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
3950 							if (node.isEmpty(nonEmptyElements)) {
3951 								if (elementRule.paddEmpty)
3952 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
3953 								else {
3954 									// Leave nodes that have a name like <a name="name">
3955 									if (!node.attributes.map.name && !node.attributes.map.id) {
3956 										tempNode = node.parent;
3957 										node.empty().remove();
3958 										node = tempNode;
3959 										return;
3960 									}
3961 								}
3962 							}
3963 						}
3964 
3965 						node = node.parent;
3966 					}
3967 				}
3968 			}, schema);
3969 
3970 			rootNode = node = new Node(args.context || settings.root_name, 11);
3971 
3972 			parser.parse(html);
3973 
3974 			// Fix invalid children or report invalid children in a contextual parsing
3975 			if (validate && invalidChildren.length) {
3976 				if (!args.context)
3977 					fixInvalidChildren(invalidChildren);
3978 				else
3979 					args.invalid = true;
3980 			}
3981 
3982 			// Wrap nodes in the root into block elements if the root is body
3983 			if (rootBlockName && rootNode.name == 'body')
3984 				addRootBlocks();
3985 
3986 			// Run filters only when the contents is valid
3987 			if (!args.invalid) {
3988 				// Run node filters
3989 				for (name in matchedNodes) {
3990 					list = nodeFilters[name];
3991 					nodes = matchedNodes[name];
3992 
3993 					// Remove already removed children
3994 					fi = nodes.length;
3995 					while (fi--) {
3996 						if (!nodes[fi].parent)
3997 							nodes.splice(fi, 1);
3998 					}
3999 
4000 					for (i = 0, l = list.length; i < l; i++)
4001 						list[i](nodes, name, args);
4002 				}
4003 
4004 				// Run attribute filters
4005 				for (i = 0, l = attributeFilters.length; i < l; i++) {
4006 					list = attributeFilters[i];
4007 
4008 					if (list.name in matchedAttributes) {
4009 						nodes = matchedAttributes[list.name];
4010 
4011 						// Remove already removed children
4012 						fi = nodes.length;
4013 						while (fi--) {
4014 							if (!nodes[fi].parent)
4015 								nodes.splice(fi, 1);
4016 						}
4017 
4018 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
4019 							list.callbacks[fi](nodes, list.name, args);
4020 					}
4021 				}
4022 			}
4023 
4024 			return rootNode;
4025 		};
4026 
4027 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4028 		// make it possible to place the caret inside empty blocks. This logic tries to remove
4029 		// these elements and keep br elements that where intended to be there intact
4030 		if (settings.remove_trailing_brs) {
4031 			self.addNodeFilter('br', function(nodes, name) {
4032 				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4033 					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4034 
4035 				// Remove brs from body element as well
4036 				blockElements.body = 1;
4037 
4038 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4039 				for (i = 0; i < l; i++) {
4040 					node = nodes[i];
4041 					parent = node.parent;
4042 
4043 					if (blockElements[node.parent.name] && node === parent.lastChild) {
4044 						// Loop all nodes to the left of the current node and check for other BR elements
4045 						// excluding bookmarks since they are invisible
4046 						prev = node.prev;
4047 						while (prev) {
4048 							prevName = prev.name;
4049 
4050 							// Ignore bookmarks
4051 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4052 								// Found a non BR element
4053 								if (prevName !== "br")
4054 									break;
4055 	
4056 								// Found another br it's a <br><br> structure then don't remove anything
4057 								if (prevName === 'br') {
4058 									node = null;
4059 									break;
4060 								}
4061 							}
4062 
4063 							prev = prev.prev;
4064 						}
4065 
4066 						if (node) {
4067 							node.remove();
4068 
4069 							// Is the parent to be considered empty after we removed the BR
4070 							if (parent.isEmpty(nonEmptyElements)) {
4071 								elementRule = schema.getElementRule(parent.name);
4072 
4073 								// Remove or padd the element depending on schema rule
4074 								if (elementRule) {
4075 									if (elementRule.removeEmpty)
4076 										parent.remove();
4077 									else if (elementRule.paddEmpty)
4078 										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4079 								}
4080 							}
4081 						}
4082 					} else {
4083 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 
4084 						lastParent = node;
4085 						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4086 							lastParent = parent;
4087 
4088 							if (blockElements[parent.name]) {
4089 								break;
4090 							}
4091 
4092 							parent = parent.parent;
4093 						}
4094 
4095 						if (lastParent === parent) {
4096 							textNode = new tinymce.html.Node('#text', 3);
4097 							textNode.value = '\u00a0';
4098 							node.replace(textNode);
4099 						}
4100 					}
4101 				}
4102 			});
4103 		}
4104 
4105 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4106 		if (!settings.allow_html_in_named_anchor) {
4107 			self.addAttributeFilter('id,name', function(nodes, name) {
4108 				var i = nodes.length, sibling, prevSibling, parent, node;
4109 
4110 				while (i--) {
4111 					node = nodes[i];
4112 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4113 						parent = node.parent;
4114 
4115 						// Move children after current node
4116 						sibling = node.lastChild;
4117 						do {
4118 							prevSibling = sibling.prev;
4119 							parent.insert(sibling, node);
4120 							sibling = prevSibling;
4121 						} while (sibling);
4122 					}
4123 				}
4124 			});
4125 		}
4126 	}
4127 })(tinymce);
4128 
4129 tinymce.html.Writer = function(settings) {
4130 	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4131 
4132 	settings = settings || {};
4133 	indent = settings.indent;
4134 	indentBefore = tinymce.makeMap(settings.indent_before || '');
4135 	indentAfter = tinymce.makeMap(settings.indent_after || '');
4136 	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4137 	htmlOutput = settings.element_format == "html";
4138 
4139 	return {
4140 		start: function(name, attrs, empty) {
4141 			var i, l, attr, value;
4142 
4143 			if (indent && indentBefore[name] && html.length > 0) {
4144 				value = html[html.length - 1];
4145 
4146 				if (value.length > 0 && value !== '\n')
4147 					html.push('\n');
4148 			}
4149 
4150 			html.push('<', name);
4151 
4152 			if (attrs) {
4153 				for (i = 0, l = attrs.length; i < l; i++) {
4154 					attr = attrs[i];
4155 					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4156 				}
4157 			}
4158 
4159 			if (!empty || htmlOutput)
4160 				html[html.length] = '>';
4161 			else
4162 				html[html.length] = ' />';
4163 
4164 			if (empty && indent && indentAfter[name] && html.length > 0) {
4165 				value = html[html.length - 1];
4166 
4167 				if (value.length > 0 && value !== '\n')
4168 					html.push('\n');
4169 			}
4170 		},
4171 
4172 		end: function(name) {
4173 			var value;
4174 
4175 			/*if (indent && indentBefore[name] && html.length > 0) {
4176 				value = html[html.length - 1];
4177 
4178 				if (value.length > 0 && value !== '\n')
4179 					html.push('\n');
4180 			}*/
4181 
4182 			html.push('</', name, '>');
4183 
4184 			if (indent && indentAfter[name] && html.length > 0) {
4185 				value = html[html.length - 1];
4186 
4187 				if (value.length > 0 && value !== '\n')
4188 					html.push('\n');
4189 			}
4190 		},
4191 
4192 		text: function(text, raw) {
4193 			if (text.length > 0)
4194 				html[html.length] = raw ? text : encode(text);
4195 		},
4196 
4197 		cdata: function(text) {
4198 			html.push('<![CDATA[', text, ']]>');
4199 		},
4200 
4201 		comment: function(text) {
4202 			html.push('<!--', text, '-->');
4203 		},
4204 
4205 		pi: function(name, text) {
4206 			if (text)
4207 				html.push('<?', name, ' ', text, '?>');
4208 			else
4209 				html.push('<?', name, '?>');
4210 
4211 			if (indent)
4212 				html.push('\n');
4213 		},
4214 
4215 		doctype: function(text) {
4216 			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4217 		},
4218 
4219 		reset: function() {
4220 			html.length = 0;
4221 		},
4222 
4223 		getContent: function() {
4224 			return html.join('').replace(/\n$/, '');
4225 		}
4226 	};
4227 };
4228 
4229 (function(tinymce) {
4230 	tinymce.html.Serializer = function(settings, schema) {
4231 		var self = this, writer = new tinymce.html.Writer(settings);
4232 
4233 		settings = settings || {};
4234 		settings.validate = "validate" in settings ? settings.validate : true;
4235 
4236 		self.schema = schema = schema || new tinymce.html.Schema();
4237 		self.writer = writer;
4238 
4239 		self.serialize = function(node) {
4240 			var handlers, validate;
4241 
4242 			validate = settings.validate;
4243 
4244 			handlers = {
4245 				// #text
4246 				3: function(node, raw) {
4247 					writer.text(node.value, node.raw);
4248 				},
4249 
4250 				// #comment
4251 				8: function(node) {
4252 					writer.comment(node.value);
4253 				},
4254 
4255 				// Processing instruction
4256 				7: function(node) {
4257 					writer.pi(node.name, node.value);
4258 				},
4259 
4260 				// Doctype
4261 				10: function(node) {
4262 					writer.doctype(node.value);
4263 				},
4264 
4265 				// CDATA
4266 				4: function(node) {
4267 					writer.cdata(node.value);
4268 				},
4269 
4270 				// Document fragment
4271 				11: function(node) {
4272 					if ((node = node.firstChild)) {
4273 						do {
4274 							walk(node);
4275 						} while (node = node.next);
4276 					}
4277 				}
4278 			};
4279 
4280 			writer.reset();
4281 
4282 			function walk(node) {
4283 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4284 
4285 				if (!handler) {
4286 					name = node.name;
4287 					isEmpty = node.shortEnded;
4288 					attrs = node.attributes;
4289 
4290 					// Sort attributes
4291 					if (validate && attrs && attrs.length > 1) {
4292 						sortedAttrs = [];
4293 						sortedAttrs.map = {};
4294 
4295 						elementRule = schema.getElementRule(node.name);
4296 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4297 							attrName = elementRule.attributesOrder[i];
4298 
4299 							if (attrName in attrs.map) {
4300 								attrValue = attrs.map[attrName];
4301 								sortedAttrs.map[attrName] = attrValue;
4302 								sortedAttrs.push({name: attrName, value: attrValue});
4303 							}
4304 						}
4305 
4306 						for (i = 0, l = attrs.length; i < l; i++) {
4307 							attrName = attrs[i].name;
4308 
4309 							if (!(attrName in sortedAttrs.map)) {
4310 								attrValue = attrs.map[attrName];
4311 								sortedAttrs.map[attrName] = attrValue;
4312 								sortedAttrs.push({name: attrName, value: attrValue});
4313 							}
4314 						}
4315 
4316 						attrs = sortedAttrs;
4317 					}
4318 
4319 					writer.start(node.name, attrs, isEmpty);
4320 
4321 					if (!isEmpty) {
4322 						if ((node = node.firstChild)) {
4323 							do {
4324 								walk(node);
4325 							} while (node = node.next);
4326 						}
4327 
4328 						writer.end(name);
4329 					}
4330 				} else
4331 					handler(node);
4332 			}
4333 
4334 			// Serialize element and treat all non elements as fragments
4335 			if (node.type == 1 && !settings.inner)
4336 				walk(node);
4337 			else
4338 				handlers[11](node);
4339 
4340 			return writer.getContent();
4341 		};
4342 	}
4343 })(tinymce);
4344 
4345 // JSLint defined globals
4346 /*global tinymce:false, window:false */
4347 
4348 tinymce.dom = {};
4349 
4350 (function(namespace, expando) {
4351 	var w3cEventModel = !!document.addEventListener;
4352 
4353 	function addEvent(target, name, callback, capture) {
4354 		if (target.addEventListener) {
4355 			target.addEventListener(name, callback, capture || false);
4356 		} else if (target.attachEvent) {
4357 			target.attachEvent('on' + name, callback);
4358 		}
4359 	}
4360 
4361 	function removeEvent(target, name, callback, capture) {
4362 		if (target.removeEventListener) {
4363 			target.removeEventListener(name, callback, capture || false);
4364 		} else if (target.detachEvent) {
4365 			target.detachEvent('on' + name, callback);
4366 		}
4367 	}
4368 
4369 	function fix(original_event, data) {
4370 		var name, event = data || {};
4371 
4372 		// Dummy function that gets replaced on the delegation state functions
4373 		function returnFalse() {
4374 			return false;
4375 		}
4376 
4377 		// Dummy function that gets replaced on the delegation state functions
4378 		function returnTrue() {
4379 			return true;
4380 		}
4381 
4382 		// Copy all properties from the original event
4383 		for (name in original_event) {
4384 			// layerX/layerY is deprecated in Chrome and produces a warning
4385 			if (name !== "layerX" && name !== "layerY") {
4386 				event[name] = original_event[name];
4387 			}
4388 		}
4389 
4390 		// Normalize target IE uses srcElement
4391 		if (!event.target) {
4392 			event.target = event.srcElement || document;
4393 		}
4394 
4395 		// Add preventDefault method
4396 		event.preventDefault = function() {
4397 			event.isDefaultPrevented = returnTrue;
4398 
4399 			// Execute preventDefault on the original event object
4400 			if (original_event) {
4401 				if (original_event.preventDefault) {
4402 					original_event.preventDefault();
4403 				} else {
4404 					original_event.returnValue = false; // IE
4405 				}
4406 			}
4407 		};
4408 
4409 		// Add stopPropagation
4410 		event.stopPropagation = function() {
4411 			event.isPropagationStopped = returnTrue;
4412 
4413 			// Execute stopPropagation on the original event object
4414 			if (original_event) {
4415 				if (original_event.stopPropagation) {
4416 					original_event.stopPropagation();
4417 				} else {
4418 					original_event.cancelBubble = true; // IE
4419 				}
4420 			 }
4421 		};
4422 
4423 		// Add stopImmediatePropagation
4424 		event.stopImmediatePropagation = function() {
4425 			event.isImmediatePropagationStopped = returnTrue;
4426 			event.stopPropagation();
4427 		};
4428 
4429 		// Add event delegation states
4430 		if (!event.isDefaultPrevented) {
4431 			event.isDefaultPrevented = returnFalse;
4432 			event.isPropagationStopped = returnFalse;
4433 			event.isImmediatePropagationStopped = returnFalse;
4434 		}
4435 
4436 		return event;
4437 	}
4438 
4439 	function bindOnReady(win, callback, event_utils) {
4440 		var doc = win.document, event = {type: 'ready'};
4441 
4442 		// Gets called when the DOM is ready
4443 		function readyHandler() {
4444 			if (!event_utils.domLoaded) {
4445 				event_utils.domLoaded = true;
4446 				callback(event);
4447 			}
4448 		}
4449 
4450 		// Use W3C method
4451 		if (w3cEventModel) {
4452 			addEvent(win, 'DOMContentLoaded', readyHandler);
4453 		} else {
4454 			// Use IE method
4455 			addEvent(doc, "readystatechange", function() {
4456 				if (doc.readyState === "complete") {
4457 					removeEvent(doc, "readystatechange", arguments.callee);
4458 					readyHandler();
4459 				}
4460 			});
4461 
4462 			// Wait until we can scroll, when we can the DOM is initialized
4463 			if (doc.documentElement.doScroll && win === win.top) {
4464 				(function() {
4465 					try {
4466 						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4467 						// http://javascript.nwbox.com/IEContentLoaded/
4468 						doc.documentElement.doScroll("left");
4469 					} catch (ex) {
4470 						setTimeout(arguments.callee, 0);
4471 						return;
4472 					}
4473 
4474 					readyHandler();
4475 				})();
4476 			}
4477 		}
4478 
4479 		// Fallback if any of the above methods should fail for some odd reason
4480 		addEvent(win, 'load', readyHandler);
4481 	}
4482 
4483 	function EventUtils(proxy) {
4484 		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4485 
4486 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4487 		hasFocusIn = "onfocusin" in document.documentElement;
4488 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4489 		count = 1;
4490 
4491 		// State if the DOMContentLoaded was executed or not
4492 		self.domLoaded = false;
4493 		self.events = events;
4494 
4495 		function executeHandlers(evt, id) {
4496 			var callbackList, i, l, callback;
4497 
4498 			callbackList = events[id][evt.type];
4499 			if (callbackList) {
4500 				for (i = 0, l = callbackList.length; i < l; i++) {
4501 					callback = callbackList[i];
4502 					
4503 					// Check if callback exists might be removed if a unbind is called inside the callback
4504 					if (callback && callback.func.call(callback.scope, evt) === false) {
4505 						evt.preventDefault();
4506 					}
4507 
4508 					// Should we stop propagation to immediate listeners
4509 					if (evt.isImmediatePropagationStopped()) {
4510 						return;
4511 					}
4512 				}
4513 			}
4514 		}
4515 
4516 		self.bind = function(target, names, callback, scope) {
4517 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4518 
4519 			// Native event handler function patches the event and executes the callbacks for the expando
4520 			function defaultNativeHandler(evt) {
4521 				executeHandlers(fix(evt || win.event), id);
4522 			}
4523 
4524 			// Don't bind to text nodes or comments
4525 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4526 				return;
4527 			}
4528 
4529 			// Create or get events id for the target
4530 			if (!target[expando]) {
4531 				id = count++;
4532 				target[expando] = id;
4533 				events[id] = {};
4534 			} else {
4535 				id = target[expando];
4536 
4537 				if (!events[id]) {
4538 					events[id] = {};
4539 				}
4540 			}
4541 
4542 			// Setup the specified scope or use the target as a default
4543 			scope = scope || target;
4544 
4545 			// Split names and bind each event, enables you to bind multiple events with one call
4546 			names = names.split(' ');
4547 			i = names.length;
4548 			while (i--) {
4549 				name = names[i];
4550 				nativeHandler = defaultNativeHandler;
4551 				fakeName = capture = false;
4552 
4553 				// Use ready instead of DOMContentLoaded
4554 				if (name === "DOMContentLoaded") {
4555 					name = "ready";
4556 				}
4557 
4558 				// DOM is already ready
4559 				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4560 					self.domLoaded = true;
4561 					callback.call(scope, fix({type: name}));
4562 					continue;
4563 				}
4564 
4565 				// Handle mouseenter/mouseleaver
4566 				if (!hasMouseEnterLeave) {
4567 					fakeName = mouseEnterLeave[name];
4568 
4569 					if (fakeName) {
4570 						nativeHandler = function(evt) {
4571 							var current, related;
4572 
4573 							current = evt.currentTarget;
4574 							related = evt.relatedTarget;
4575 
4576 							// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4577 							if (related && current.contains) {
4578 								// Use contains for performance
4579 								related = current.contains(related);
4580 							} else {
4581 								while (related && related !== current) {
4582 									related = related.parentNode;
4583 								}
4584 							}
4585 
4586 							// Fire fake event
4587 							if (!related) {
4588 								evt = fix(evt || win.event);
4589 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4590 								evt.target = current;
4591 								executeHandlers(evt, id);
4592 							}
4593 						};
4594 					}
4595 				}
4596 
4597 				// Fake bubbeling of focusin/focusout
4598 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4599 					capture = true;
4600 					fakeName = name === "focusin" ? "focus" : "blur";
4601 					nativeHandler = function(evt) {
4602 						evt = fix(evt || win.event);
4603 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4604 						executeHandlers(evt, id);
4605 					};
4606 				}
4607 
4608 				// Setup callback list and bind native event
4609 				callbackList = events[id][name];
4610 				if (!callbackList) {
4611 					events[id][name] = callbackList = [{func: callback, scope: scope}];
4612 					callbackList.fakeName = fakeName;
4613 					callbackList.capture = capture;
4614 
4615 					// Add the nativeHandler to the callback list so that we can later unbind it
4616 					callbackList.nativeHandler = nativeHandler;
4617 					if (!w3cEventModel) {
4618 						callbackList.proxyHandler = proxy(id);
4619 					}
4620 
4621 					// Check if the target has native events support
4622 					if (name === "ready") {
4623 						bindOnReady(target, nativeHandler, self);
4624 					} else {
4625 						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4626 					}
4627 				} else {
4628 					// If it already has an native handler then just push the callback
4629 					callbackList.push({func: callback, scope: scope});
4630 				}
4631 			}
4632 
4633 			target = callbackList = 0; // Clean memory for IE
4634 
4635 			return callback;
4636 		};
4637 
4638 		self.unbind = function(target, names, callback) {
4639 			var id, callbackList, i, ci, name, eventMap;
4640 
4641 			// Don't bind to text nodes or comments
4642 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4643 				return self;
4644 			}
4645 
4646 			// Unbind event or events if the target has the expando
4647 			id = target[expando];
4648 			if (id) {
4649 				eventMap = events[id];
4650 
4651 				// Specific callback
4652 				if (names) {
4653 					names = names.split(' ');
4654 					i = names.length;
4655 					while (i--) {
4656 						name = names[i];
4657 						callbackList = eventMap[name];
4658 
4659 						// Unbind the event if it exists in the map
4660 						if (callbackList) {
4661 							// Remove specified callback
4662 							if (callback) {
4663 								ci = callbackList.length;
4664 								while (ci--) {
4665 									if (callbackList[ci].func === callback) {
4666 										callbackList.splice(ci, 1);
4667 									}
4668 								}
4669 							}
4670 
4671 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4672 							if (!callback || callbackList.length === 0) {
4673 								delete eventMap[name];
4674 								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4675 							}
4676 						}
4677 					}
4678 				} else {
4679 					// All events for a specific element
4680 					for (name in eventMap) {
4681 						callbackList = eventMap[name];
4682 						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4683 					}
4684 
4685 					eventMap = {};
4686 				}
4687 
4688 				// Check if object is empty, if it isn't then we won't remove the expando map
4689 				for (name in eventMap) {
4690 					return self;
4691 				}
4692 
4693 				// Delete event object
4694 				delete events[id];
4695 
4696 				// Remove expando from target
4697 				try {
4698 					// IE will fail here since it can't delete properties from window
4699 					delete target[expando];
4700 				} catch (ex) {
4701 					// IE will set it to null
4702 					target[expando] = null;
4703 				}
4704 			}
4705 
4706 			return self;
4707 		};
4708 
4709 		self.fire = function(target, name, args) {
4710 			var id, event;
4711 
4712 			// Don't bind to text nodes or comments
4713 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4714 				return self;
4715 			}
4716 
4717 			// Build event object by patching the args
4718 			event = fix(null, args);
4719 			event.type = name;
4720 
4721 			do {
4722 				// Found an expando that means there is listeners to execute
4723 				id = target[expando];
4724 				if (id) {
4725 					executeHandlers(event, id);
4726 				}
4727 
4728 				// Walk up the DOM
4729 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4730 			} while (target && !event.isPropagationStopped());
4731 
4732 			return self;
4733 		};
4734 
4735 		self.clean = function(target) {
4736 			var i, children, unbind = self.unbind;
4737 	
4738 			// Don't bind to text nodes or comments
4739 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4740 				return self;
4741 			}
4742 
4743 			// Unbind any element on the specificed target
4744 			if (target[expando]) {
4745 				unbind(target);
4746 			}
4747 
4748 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4749 			if (!target.getElementsByTagName) {
4750 				target = target.document;
4751 			}
4752 
4753 			// Remove events from each child element
4754 			if (target && target.getElementsByTagName) {
4755 				unbind(target);
4756 
4757 				children = target.getElementsByTagName('*');
4758 				i = children.length;
4759 				while (i--) {
4760 					target = children[i];
4761 
4762 					if (target[expando]) {
4763 						unbind(target);
4764 					}
4765 				}
4766 			}
4767 
4768 			return self;
4769 		};
4770 
4771 		self.callNativeHandler = function(id, evt) {
4772 			if (events) {
4773 				events[id][evt.type].nativeHandler(evt);
4774 			}
4775 		};
4776 
4777 		self.destory = function() {
4778 			events = {};
4779 		};
4780 
4781 		// Legacy function calls
4782 
4783 		self.add = function(target, events, func, scope) {
4784 			// Old API supported direct ID assignment
4785 			if (typeof(target) === "string") {
4786 				target = document.getElementById(target);
4787 			}
4788 
4789 			// Old API supported multiple targets
4790 			if (target && target instanceof Array) {
4791 				var i = target.length;
4792 
4793 				while (i--) {
4794 					self.add(target[i], events, func, scope);
4795 				}
4796 
4797 				return;
4798 			}
4799 
4800 			// Old API called ready init
4801 			if (events === "init") {
4802 				events = "ready";
4803 			}
4804 
4805 			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
4806 		};
4807 
4808 		self.remove = function(target, events, func, scope) {
4809 			if (!target) {
4810 				return self;
4811 			}
4812 
4813 			// Old API supported direct ID assignment
4814 			if (typeof(target) === "string") {
4815 				target = document.getElementById(target);
4816 			}
4817 
4818 			// Old API supported multiple targets
4819 			if (target instanceof Array) {
4820 				var i = target.length;
4821 
4822 				while (i--) {
4823 					self.remove(target[i], events, func, scope);
4824 				}
4825 
4826 				return self;
4827 			}
4828 
4829 			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
4830 		};
4831 
4832 		self.clear = function(target) {
4833 			// Old API supported direct ID assignment
4834 			if (typeof(target) === "string") {
4835 				target = document.getElementById(target);
4836 			}
4837 
4838 			return self.clean(target);
4839 		};
4840 
4841 		self.cancel = function(e) {
4842 			if (e) {
4843 				self.prevent(e);
4844 				self.stop(e);
4845 			}
4846 
4847 			return false;
4848 		};
4849 
4850 		self.prevent = function(e) {
4851 			if (!e.preventDefault) {
4852 				e = fix(e);
4853 			}
4854 
4855 			e.preventDefault();
4856 
4857 			return false;
4858 		};
4859 
4860 		self.stop = function(e) {
4861 			if (!e.stopPropagation) {
4862 				e = fix(e);
4863 			}
4864 
4865 			e.stopPropagation();
4866 
4867 			return false;
4868 		};
4869 	}
4870 
4871 	namespace.EventUtils = EventUtils;
4872 
4873 	namespace.Event = new EventUtils(function(id) {
4874 		return function(evt) {
4875 			tinymce.dom.Event.callNativeHandler(id, evt);
4876 		};
4877 	});
4878 
4879 	// Bind ready event when tinymce script is loaded
4880 	namespace.Event.bind(window, 'ready', function() {});
4881 
4882 	namespace = 0;
4883 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
4884 
4885 tinymce.dom.TreeWalker = function(start_node, root_node) {
4886 	var node = start_node;
4887 
4888 	function findSibling(node, start_name, sibling_name, shallow) {
4889 		var sibling, parent;
4890 
4891 		if (node) {
4892 			// Walk into nodes if it has a start
4893 			if (!shallow && node[start_name])
4894 				return node[start_name];
4895 
4896 			// Return the sibling if it has one
4897 			if (node != root_node) {
4898 				sibling = node[sibling_name];
4899 				if (sibling)
4900 					return sibling;
4901 
4902 				// Walk up the parents to look for siblings
4903 				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
4904 					sibling = parent[sibling_name];
4905 					if (sibling)
4906 						return sibling;
4907 				}
4908 			}
4909 		}
4910 	};
4911 
4912 	this.current = function() {
4913 		return node;
4914 	};
4915 
4916 	this.next = function(shallow) {
4917 		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
4918 	};
4919 
4920 	this.prev = function(shallow) {
4921 		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
4922 	};
4923 };
4924 
4925 (function(tinymce) {
4926 	// Shorten names
4927 	var each = tinymce.each,
4928 		is = tinymce.is,
4929 		isWebKit = tinymce.isWebKit,
4930 		isIE = tinymce.isIE,
4931 		Entities = tinymce.html.Entities,
4932 		simpleSelectorRe = /^([a-z0-9],?)+$/i,
4933 		whiteSpaceRegExp = /^[ \t\r\n]*$/;
4934 
4935 	tinymce.create('tinymce.dom.DOMUtils', {
4936 		doc : null,
4937 		root : null,
4938 		files : null,
4939 		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
4940 		props : {
4941 			"for" : "htmlFor",
4942 			"class" : "className",
4943 			className : "className",
4944 			checked : "checked",
4945 			disabled : "disabled",
4946 			maxlength : "maxLength",
4947 			readonly : "readOnly",
4948 			selected : "selected",
4949 			value : "value",
4950 			id : "id",
4951 			name : "name",
4952 			type : "type"
4953 		},
4954 
4955 		DOMUtils : function(d, s) {
4956 			var t = this, globalStyle, name, blockElementsMap;
4957 
4958 			t.doc = d;
4959 			t.win = window;
4960 			t.files = {};
4961 			t.cssFlicker = false;
4962 			t.counter = 0;
4963 			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
4964 			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
4965 			t.hasOuterHTML = "outerHTML" in d.createElement("a");
4966 
4967 			t.settings = s = tinymce.extend({
4968 				keep_values : false,
4969 				hex_colors : 1
4970 			}, s);
4971 			
4972 			t.schema = s.schema;
4973 			t.styles = new tinymce.html.Styles({
4974 				url_converter : s.url_converter,
4975 				url_converter_scope : s.url_converter_scope
4976 			}, s.schema);
4977 
4978 			// Fix IE6SP2 flicker and check it failed for pre SP2
4979 			if (tinymce.isIE6) {
4980 				try {
4981 					d.execCommand('BackgroundImageCache', false, true);
4982 				} catch (e) {
4983 					t.cssFlicker = true;
4984 				}
4985 			}
4986 
4987 			t.fixDoc(d);
4988 			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
4989 			tinymce.addUnload(t.destroy, t);
4990 			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
4991 
4992 			t.isBlock = function(node) {
4993 				// This function is called in module pattern style since it might be executed with the wrong this scope
4994 				var type = node.nodeType;
4995 
4996 				// If it's a node then check the type and use the nodeName
4997 				if (type)
4998 					return !!(type === 1 && blockElementsMap[node.nodeName]);
4999 
5000 				return !!blockElementsMap[node];
5001 			};
5002 		},
5003 
5004 		fixDoc: function(doc) {
5005 			var settings = this.settings, name;
5006 
5007 			if (isIE && settings.schema) {
5008 				// Add missing HTML 4/5 elements to IE
5009 				('abbr article aside audio canvas ' +
5010 				'details figcaption figure footer ' +
5011 				'header hgroup mark menu meter nav ' +
5012 				'output progress section summary ' +
5013 				'time video').replace(/\w+/g, function(name) {
5014 					doc.createElement(name);
5015 				});
5016 
5017 				// Create all custom elements
5018 				for (name in settings.schema.getCustomElements()) {
5019 					doc.createElement(name);
5020 				}
5021 			}
5022 		},
5023 
5024 		clone: function(node, deep) {
5025 			var self = this, clone, doc;
5026 
5027 			// TODO: Add feature detection here in the future
5028 			if (!isIE || node.nodeType !== 1 || deep) {
5029 				return node.cloneNode(deep);
5030 			}
5031 
5032 			doc = self.doc;
5033 
5034 			// Make a HTML5 safe shallow copy
5035 			if (!deep) {
5036 				clone = doc.createElement(node.nodeName);
5037 
5038 				// Copy attribs
5039 				each(self.getAttribs(node), function(attr) {
5040 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5041 				});
5042 
5043 				return clone;
5044 			}
5045 /*
5046 			// Setup HTML5 patched document fragment
5047 			if (!self.frag) {
5048 				self.frag = doc.createDocumentFragment();
5049 				self.fixDoc(self.frag);
5050 			}
5051 
5052 			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5053 			clone = doc.createElement('div');
5054 			self.frag.appendChild(clone);
5055 			clone.innerHTML = node.outerHTML;
5056 			self.frag.removeChild(clone);
5057 */
5058 			return clone.firstChild;
5059 		},
5060 
5061 		getRoot : function() {
5062 			var t = this, s = t.settings;
5063 
5064 			return (s && t.get(s.root_element)) || t.doc.body;
5065 		},
5066 
5067 		getViewPort : function(w) {
5068 			var d, b;
5069 
5070 			w = !w ? this.win : w;
5071 			d = w.document;
5072 			b = this.boxModel ? d.documentElement : d.body;
5073 
5074 			// Returns viewport size excluding scrollbars
5075 			return {
5076 				x : w.pageXOffset || b.scrollLeft,
5077 				y : w.pageYOffset || b.scrollTop,
5078 				w : w.innerWidth || b.clientWidth,
5079 				h : w.innerHeight || b.clientHeight
5080 			};
5081 		},
5082 
5083 		getRect : function(e) {
5084 			var p, t = this, sr;
5085 
5086 			e = t.get(e);
5087 			p = t.getPos(e);
5088 			sr = t.getSize(e);
5089 
5090 			return {
5091 				x : p.x,
5092 				y : p.y,
5093 				w : sr.w,
5094 				h : sr.h
5095 			};
5096 		},
5097 
5098 		getSize : function(e) {
5099 			var t = this, w, h;
5100 
5101 			e = t.get(e);
5102 			w = t.getStyle(e, 'width');
5103 			h = t.getStyle(e, 'height');
5104 
5105 			// Non pixel value, then force offset/clientWidth
5106 			if (w.indexOf('px') === -1)
5107 				w = 0;
5108 
5109 			// Non pixel value, then force offset/clientWidth
5110 			if (h.indexOf('px') === -1)
5111 				h = 0;
5112 
5113 			return {
5114 				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5115 				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5116 			};
5117 		},
5118 
5119 		getParent : function(n, f, r) {
5120 			return this.getParents(n, f, r, false);
5121 		},
5122 
5123 		getParents : function(n, f, r, c) {
5124 			var t = this, na, se = t.settings, o = [];
5125 
5126 			n = t.get(n);
5127 			c = c === undefined;
5128 
5129 			if (se.strict_root)
5130 				r = r || t.getRoot();
5131 
5132 			// Wrap node name as func
5133 			if (is(f, 'string')) {
5134 				na = f;
5135 
5136 				if (f === '*') {
5137 					f = function(n) {return n.nodeType == 1;};
5138 				} else {
5139 					f = function(n) {
5140 						return t.is(n, na);
5141 					};
5142 				}
5143 			}
5144 
5145 			while (n) {
5146 				if (n == r || !n.nodeType || n.nodeType === 9)
5147 					break;
5148 
5149 				if (!f || f(n)) {
5150 					if (c)
5151 						o.push(n);
5152 					else
5153 						return n;
5154 				}
5155 
5156 				n = n.parentNode;
5157 			}
5158 
5159 			return c ? o : null;
5160 		},
5161 
5162 		get : function(e) {
5163 			var n;
5164 
5165 			if (e && this.doc && typeof(e) == 'string') {
5166 				n = e;
5167 				e = this.doc.getElementById(e);
5168 
5169 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5170 				if (e && e.id !== n)
5171 					return this.doc.getElementsByName(n)[1];
5172 			}
5173 
5174 			return e;
5175 		},
5176 
5177 		getNext : function(node, selector) {
5178 			return this._findSib(node, selector, 'nextSibling');
5179 		},
5180 
5181 		getPrev : function(node, selector) {
5182 			return this._findSib(node, selector, 'previousSibling');
5183 		},
5184 
5185 
5186 		select : function(pa, s) {
5187 			var t = this;
5188 
5189 			return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
5190 		},
5191 
5192 		is : function(n, selector) {
5193 			var i;
5194 
5195 			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5196 			if (n.length === undefined) {
5197 				// Simple all selector
5198 				if (selector === '*')
5199 					return n.nodeType == 1;
5200 
5201 				// Simple selector just elements
5202 				if (simpleSelectorRe.test(selector)) {
5203 					selector = selector.toLowerCase().split(/,/);
5204 					n = n.nodeName.toLowerCase();
5205 
5206 					for (i = selector.length - 1; i >= 0; i--) {
5207 						if (selector[i] == n)
5208 							return true;
5209 					}
5210 
5211 					return false;
5212 				}
5213 			}
5214 
5215 			return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
5216 		},
5217 
5218 
5219 		add : function(p, n, a, h, c) {
5220 			var t = this;
5221 
5222 			return this.run(p, function(p) {
5223 				var e, k;
5224 
5225 				e = is(n, 'string') ? t.doc.createElement(n) : n;
5226 				t.setAttribs(e, a);
5227 
5228 				if (h) {
5229 					if (h.nodeType)
5230 						e.appendChild(h);
5231 					else
5232 						t.setHTML(e, h);
5233 				}
5234 
5235 				return !c ? p.appendChild(e) : e;
5236 			});
5237 		},
5238 
5239 		create : function(n, a, h) {
5240 			return this.add(this.doc.createElement(n), n, a, h, 1);
5241 		},
5242 
5243 		createHTML : function(n, a, h) {
5244 			var o = '', t = this, k;
5245 
5246 			o += '<' + n;
5247 
5248 			for (k in a) {
5249 				if (a.hasOwnProperty(k))
5250 					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5251 			}
5252 
5253 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5254 			if (typeof(h) != "undefined")
5255 				return o + '>' + h + '</' + n + '>';
5256 
5257 			return o + ' />';
5258 		},
5259 
5260 		remove : function(node, keep_children) {
5261 			return this.run(node, function(node) {
5262 				var child, parent = node.parentNode;
5263 
5264 				if (!parent)
5265 					return null;
5266 
5267 				if (keep_children) {
5268 					while (child = node.firstChild) {
5269 						// IE 8 will crash if you don't remove completely empty text nodes
5270 						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5271 							parent.insertBefore(child, node);
5272 						else
5273 							node.removeChild(child);
5274 					}
5275 				}
5276 
5277 				return parent.removeChild(node);
5278 			});
5279 		},
5280 
5281 		setStyle : function(n, na, v) {
5282 			var t = this;
5283 
5284 			return t.run(n, function(e) {
5285 				var s, i;
5286 
5287 				s = e.style;
5288 
5289 				// Camelcase it, if needed
5290 				na = na.replace(/-(\D)/g, function(a, b){
5291 					return b.toUpperCase();
5292 				});
5293 
5294 				// Default px suffix on these
5295 				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5296 					v += 'px';
5297 
5298 				switch (na) {
5299 					case 'opacity':
5300 						// IE specific opacity
5301 						if (isIE) {
5302 							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5303 
5304 							if (!n.currentStyle || !n.currentStyle.hasLayout)
5305 								s.display = 'inline-block';
5306 						}
5307 
5308 						// Fix for older browsers
5309 						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5310 						break;
5311 
5312 					case 'float':
5313 						isIE ? s.styleFloat = v : s.cssFloat = v;
5314 						break;
5315 					
5316 					default:
5317 						s[na] = v || '';
5318 				}
5319 
5320 				// Force update of the style data
5321 				if (t.settings.update_styles)
5322 					t.setAttrib(e, 'data-mce-style');
5323 			});
5324 		},
5325 
5326 		getStyle : function(n, na, c) {
5327 			n = this.get(n);
5328 
5329 			if (!n)
5330 				return;
5331 
5332 			// Gecko
5333 			if (this.doc.defaultView && c) {
5334 				// Remove camelcase
5335 				na = na.replace(/[A-Z]/g, function(a){
5336 					return '-' + a;
5337 				});
5338 
5339 				try {
5340 					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5341 				} catch (ex) {
5342 					// Old safari might fail
5343 					return null;
5344 				}
5345 			}
5346 
5347 			// Camelcase it, if needed
5348 			na = na.replace(/-(\D)/g, function(a, b){
5349 				return b.toUpperCase();
5350 			});
5351 
5352 			if (na == 'float')
5353 				na = isIE ? 'styleFloat' : 'cssFloat';
5354 
5355 			// IE & Opera
5356 			if (n.currentStyle && c)
5357 				return n.currentStyle[na];
5358 
5359 			return n.style ? n.style[na] : undefined;
5360 		},
5361 
5362 		setStyles : function(e, o) {
5363 			var t = this, s = t.settings, ol;
5364 
5365 			ol = s.update_styles;
5366 			s.update_styles = 0;
5367 
5368 			each(o, function(v, n) {
5369 				t.setStyle(e, n, v);
5370 			});
5371 
5372 			// Update style info
5373 			s.update_styles = ol;
5374 			if (s.update_styles)
5375 				t.setAttrib(e, s.cssText);
5376 		},
5377 
5378 		removeAllAttribs: function(e) {
5379 			return this.run(e, function(e) {
5380 				var i, attrs = e.attributes;
5381 				for (i = attrs.length - 1; i >= 0; i--) {
5382 					e.removeAttributeNode(attrs.item(i));
5383 				}
5384 			});
5385 		},
5386 
5387 		setAttrib : function(e, n, v) {
5388 			var t = this;
5389 
5390 			// Whats the point
5391 			if (!e || !n)
5392 				return;
5393 
5394 			// Strict XML mode
5395 			if (t.settings.strict)
5396 				n = n.toLowerCase();
5397 
5398 			return this.run(e, function(e) {
5399 				var s = t.settings;
5400 				var originalValue = e.getAttribute(n);
5401 				if (v !== null) {
5402 					switch (n) {
5403 						case "style":
5404 							if (!is(v, 'string')) {
5405 								each(v, function(v, n) {
5406 									t.setStyle(e, n, v);
5407 								});
5408 
5409 								return;
5410 							}
5411 
5412 							// No mce_style for elements with these since they might get resized by the user
5413 							if (s.keep_values) {
5414 								if (v && !t._isRes(v))
5415 									e.setAttribute('data-mce-style', v, 2);
5416 								else
5417 									e.removeAttribute('data-mce-style', 2);
5418 							}
5419 
5420 							e.style.cssText = v;
5421 							break;
5422 
5423 						case "class":
5424 							e.className = v || ''; // Fix IE null bug
5425 							break;
5426 
5427 						case "src":
5428 						case "href":
5429 							if (s.keep_values) {
5430 								if (s.url_converter)
5431 									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5432 
5433 								t.setAttrib(e, 'data-mce-' + n, v, 2);
5434 							}
5435 
5436 							break;
5437 
5438 						case "shape":
5439 							e.setAttribute('data-mce-style', v);
5440 							break;
5441 					}
5442 				}
5443 				if (is(v) && v !== null && v.length !== 0)
5444 					e.setAttribute(n, '' + v, 2);
5445 				else
5446 					e.removeAttribute(n, 2);
5447 
5448 				// fire onChangeAttrib event for attributes that have changed
5449 				if (tinyMCE.activeEditor && originalValue != v) {
5450 					var ed = tinyMCE.activeEditor;
5451 					ed.onSetAttrib.dispatch(ed, e, n, v);
5452 				}
5453 			});
5454 		},
5455 
5456 		setAttribs : function(e, o) {
5457 			var t = this;
5458 
5459 			return this.run(e, function(e) {
5460 				each(o, function(v, n) {
5461 					t.setAttrib(e, n, v);
5462 				});
5463 			});
5464 		},
5465 
5466 		getAttrib : function(e, n, dv) {
5467 			var v, t = this, undef;
5468 
5469 			e = t.get(e);
5470 
5471 			if (!e || e.nodeType !== 1)
5472 				return dv === undef ? false : dv;
5473 
5474 			if (!is(dv))
5475 				dv = '';
5476 
5477 			// Try the mce variant for these
5478 			if (/^(src|href|style|coords|shape)$/.test(n)) {
5479 				v = e.getAttribute("data-mce-" + n);
5480 
5481 				if (v)
5482 					return v;
5483 			}
5484 
5485 			if (isIE && t.props[n]) {
5486 				v = e[t.props[n]];
5487 				v = v && v.nodeValue ? v.nodeValue : v;
5488 			}
5489 
5490 			if (!v)
5491 				v = e.getAttribute(n, 2);
5492 
5493 			// Check boolean attribs
5494 			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5495 				if (e[t.props[n]] === true && v === '')
5496 					return n;
5497 
5498 				return v ? n : '';
5499 			}
5500 
5501 			// Inner input elements will override attributes on form elements
5502 			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5503 				return e.getAttributeNode(n).nodeValue;
5504 
5505 			if (n === 'style') {
5506 				v = v || e.style.cssText;
5507 
5508 				if (v) {
5509 					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5510 
5511 					if (t.settings.keep_values && !t._isRes(v))
5512 						e.setAttribute('data-mce-style', v);
5513 				}
5514 			}
5515 
5516 			// Remove Apple and WebKit stuff
5517 			if (isWebKit && n === "class" && v)
5518 				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5519 
5520 			// Handle IE issues
5521 			if (isIE) {
5522 				switch (n) {
5523 					case 'rowspan':
5524 					case 'colspan':
5525 						// IE returns 1 as default value
5526 						if (v === 1)
5527 							v = '';
5528 
5529 						break;
5530 
5531 					case 'size':
5532 						// IE returns +0 as default value for size
5533 						if (v === '+0' || v === 20 || v === 0)
5534 							v = '';
5535 
5536 						break;
5537 
5538 					case 'width':
5539 					case 'height':
5540 					case 'vspace':
5541 					case 'checked':
5542 					case 'disabled':
5543 					case 'readonly':
5544 						if (v === 0)
5545 							v = '';
5546 
5547 						break;
5548 
5549 					case 'hspace':
5550 						// IE returns -1 as default value
5551 						if (v === -1)
5552 							v = '';
5553 
5554 						break;
5555 
5556 					case 'maxlength':
5557 					case 'tabindex':
5558 						// IE returns default value
5559 						if (v === 32768 || v === 2147483647 || v === '32768')
5560 							v = '';
5561 
5562 						break;
5563 
5564 					case 'multiple':
5565 					case 'compact':
5566 					case 'noshade':
5567 					case 'nowrap':
5568 						if (v === 65535)
5569 							return n;
5570 
5571 						return dv;
5572 
5573 					case 'shape':
5574 						v = v.toLowerCase();
5575 						break;
5576 
5577 					default:
5578 						// IE has odd anonymous function for event attributes
5579 						if (n.indexOf('on') === 0 && v)
5580 							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5581 				}
5582 			}
5583 
5584 			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5585 		},
5586 
5587 		getPos : function(n, ro) {
5588 			var t = this, x = 0, y = 0, e, d = t.doc, r;
5589 
5590 			n = t.get(n);
5591 			ro = ro || d.body;
5592 
5593 			if (n) {
5594 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5595 				if (n.getBoundingClientRect) {
5596 					n = n.getBoundingClientRect();
5597 					e = t.boxModel ? d.documentElement : d.body;
5598 
5599 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5600 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5601 					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5602 					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5603 
5604 					return {x : x, y : y};
5605 				}
5606 
5607 				r = n;
5608 				while (r && r != ro && r.nodeType) {
5609 					x += r.offsetLeft || 0;
5610 					y += r.offsetTop || 0;
5611 					r = r.offsetParent;
5612 				}
5613 
5614 				r = n.parentNode;
5615 				while (r && r != ro && r.nodeType) {
5616 					x -= r.scrollLeft || 0;
5617 					y -= r.scrollTop || 0;
5618 					r = r.parentNode;
5619 				}
5620 			}
5621 
5622 			return {x : x, y : y};
5623 		},
5624 
5625 		parseStyle : function(st) {
5626 			return this.styles.parse(st);
5627 		},
5628 
5629 		serializeStyle : function(o, name) {
5630 			return this.styles.serialize(o, name);
5631 		},
5632 
5633 		addStyle: function(cssText) {
5634 			var doc = this.doc, head;
5635 
5636 			// Create style element if needed
5637 			styleElm = doc.getElementById('mceDefaultStyles');
5638 			if (!styleElm) {
5639 				styleElm = doc.createElement('style'),
5640 				styleElm.id = 'mceDefaultStyles';
5641 				styleElm.type = 'text/css';
5642 
5643 				head = doc.getElementsByTagName('head')[0]
5644 				if (head.firstChild) {
5645 					head.insertBefore(styleElm, head.firstChild);
5646 				} else {
5647 					head.appendChild(styleElm);
5648 				}
5649 			}
5650 
5651 			// Append style data to old or new style element
5652 			if (styleElm.styleSheet) {
5653 				styleElm.styleSheet.cssText += cssText;
5654 			} else {
5655 				styleElm.appendChild(doc.createTextNode(cssText));
5656 			}
5657 		},
5658 
5659 		loadCSS : function(u) {
5660 			var t = this, d = t.doc, head;
5661 
5662 			if (!u)
5663 				u = '';
5664 
5665 			head = d.getElementsByTagName('head')[0];
5666 
5667 			each(u.split(','), function(u) {
5668 				var link;
5669 
5670 				if (t.files[u])
5671 					return;
5672 
5673 				t.files[u] = true;
5674 				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5675 
5676 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5677 				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5678 				// It's ugly but it seems to work fine.
5679 				if (isIE && d.documentMode && d.recalc) {
5680 					link.onload = function() {
5681 						if (d.recalc)
5682 							d.recalc();
5683 
5684 						link.onload = null;
5685 					};
5686 				}
5687 
5688 				head.appendChild(link);
5689 			});
5690 		},
5691 
5692 		addClass : function(e, c) {
5693 			return this.run(e, function(e) {
5694 				var o;
5695 
5696 				if (!c)
5697 					return 0;
5698 
5699 				if (this.hasClass(e, c))
5700 					return e.className;
5701 
5702 				o = this.removeClass(e, c);
5703 
5704 				return e.className = (o != '' ? (o + ' ') : '') + c;
5705 			});
5706 		},
5707 
5708 		removeClass : function(e, c) {
5709 			var t = this, re;
5710 
5711 			return t.run(e, function(e) {
5712 				var v;
5713 
5714 				if (t.hasClass(e, c)) {
5715 					if (!re)
5716 						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5717 
5718 					v = e.className.replace(re, ' ');
5719 					v = tinymce.trim(v != ' ' ? v : '');
5720 
5721 					e.className = v;
5722 
5723 					// Empty class attr
5724 					if (!v) {
5725 						e.removeAttribute('class');
5726 						e.removeAttribute('className');
5727 					}
5728 
5729 					return v;
5730 				}
5731 
5732 				return e.className;
5733 			});
5734 		},
5735 
5736 		hasClass : function(n, c) {
5737 			n = this.get(n);
5738 
5739 			if (!n || !c)
5740 				return false;
5741 
5742 			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5743 		},
5744 
5745 		show : function(e) {
5746 			return this.setStyle(e, 'display', 'block');
5747 		},
5748 
5749 		hide : function(e) {
5750 			return this.setStyle(e, 'display', 'none');
5751 		},
5752 
5753 		isHidden : function(e) {
5754 			e = this.get(e);
5755 
5756 			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5757 		},
5758 
5759 		uniqueId : function(p) {
5760 			return (!p ? 'mce_' : p) + (this.counter++);
5761 		},
5762 
5763 		setHTML : function(element, html) {
5764 			var self = this;
5765 
5766 			return self.run(element, function(element) {
5767 				if (isIE) {
5768 					// Remove all child nodes, IE keeps empty text nodes in DOM
5769 					while (element.firstChild)
5770 						element.removeChild(element.firstChild);
5771 
5772 					try {
5773 						// IE will remove comments from the beginning
5774 						// unless you padd the contents with something
5775 						element.innerHTML = '<br />' + html;
5776 						element.removeChild(element.firstChild);
5777 					} catch (ex) {
5778 						// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
5779 						// This seems to fix this problem
5780 
5781 						// Create new div with HTML contents and a BR infront to keep comments
5782 						var newElement = self.create('div');
5783 						newElement.innerHTML = '<br />' + html;
5784 
5785 						// Add all children from div to target
5786 						each (tinymce.grep(newElement.childNodes), function(node, i) {
5787 							// Skip br element
5788 							if (i && element.canHaveHTML)
5789 								element.appendChild(node);
5790 						});
5791 					}
5792 				} else
5793 					element.innerHTML = html;
5794 
5795 				return html;
5796 			});
5797 		},
5798 
5799 		getOuterHTML : function(elm) {
5800 			var doc, self = this;
5801 
5802 			elm = self.get(elm);
5803 
5804 			if (!elm)
5805 				return null;
5806 
5807 			if (elm.nodeType === 1 && self.hasOuterHTML)
5808 				return elm.outerHTML;
5809 
5810 			doc = (elm.ownerDocument || self.doc).createElement("body");
5811 			doc.appendChild(elm.cloneNode(true));
5812 
5813 			return doc.innerHTML;
5814 		},
5815 
5816 		setOuterHTML : function(e, h, d) {
5817 			var t = this;
5818 
5819 			function setHTML(e, h, d) {
5820 				var n, tp;
5821 
5822 				tp = d.createElement("body");
5823 				tp.innerHTML = h;
5824 
5825 				n = tp.lastChild;
5826 				while (n) {
5827 					t.insertAfter(n.cloneNode(true), e);
5828 					n = n.previousSibling;
5829 				}
5830 
5831 				t.remove(e);
5832 			};
5833 
5834 			return this.run(e, function(e) {
5835 				e = t.get(e);
5836 
5837 				// Only set HTML on elements
5838 				if (e.nodeType == 1) {
5839 					d = d || e.ownerDocument || t.doc;
5840 
5841 					if (isIE) {
5842 						try {
5843 							// Try outerHTML for IE it sometimes produces an unknown runtime error
5844 							if (isIE && e.nodeType == 1)
5845 								e.outerHTML = h;
5846 							else
5847 								setHTML(e, h, d);
5848 						} catch (ex) {
5849 							// Fix for unknown runtime error
5850 							setHTML(e, h, d);
5851 						}
5852 					} else
5853 						setHTML(e, h, d);
5854 				}
5855 			});
5856 		},
5857 
5858 		decode : Entities.decode,
5859 
5860 		encode : Entities.encodeAllRaw,
5861 
5862 		insertAfter : function(node, reference_node) {
5863 			reference_node = this.get(reference_node);
5864 
5865 			return this.run(node, function(node) {
5866 				var parent, nextSibling;
5867 
5868 				parent = reference_node.parentNode;
5869 				nextSibling = reference_node.nextSibling;
5870 
5871 				if (nextSibling)
5872 					parent.insertBefore(node, nextSibling);
5873 				else
5874 					parent.appendChild(node);
5875 
5876 				return node;
5877 			});
5878 		},
5879 
5880 		replace : function(n, o, k) {
5881 			var t = this;
5882 
5883 			if (is(o, 'array'))
5884 				n = n.cloneNode(true);
5885 
5886 			return t.run(o, function(o) {
5887 				if (k) {
5888 					each(tinymce.grep(o.childNodes), function(c) {
5889 						n.appendChild(c);
5890 					});
5891 				}
5892 
5893 				return o.parentNode.replaceChild(n, o);
5894 			});
5895 		},
5896 
5897 		rename : function(elm, name) {
5898 			var t = this, newElm;
5899 
5900 			if (elm.nodeName != name.toUpperCase()) {
5901 				// Rename block element
5902 				newElm = t.create(name);
5903 
5904 				// Copy attribs to new block
5905 				each(t.getAttribs(elm), function(attr_node) {
5906 					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
5907 				});
5908 
5909 				// Replace block
5910 				t.replace(newElm, elm, 1);
5911 			}
5912 
5913 			return newElm || elm;
5914 		},
5915 
5916 		findCommonAncestor : function(a, b) {
5917 			var ps = a, pe;
5918 
5919 			while (ps) {
5920 				pe = b;
5921 
5922 				while (pe && ps != pe)
5923 					pe = pe.parentNode;
5924 
5925 				if (ps == pe)
5926 					break;
5927 
5928 				ps = ps.parentNode;
5929 			}
5930 
5931 			if (!ps && a.ownerDocument)
5932 				return a.ownerDocument.documentElement;
5933 
5934 			return ps;
5935 		},
5936 
5937 		toHex : function(s) {
5938 			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
5939 
5940 			function hex(s) {
5941 				s = parseInt(s, 10).toString(16);
5942 
5943 				return s.length > 1 ? s : '0' + s; // 0 -> 00
5944 			};
5945 
5946 			if (c) {
5947 				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
5948 
5949 				return s;
5950 			}
5951 
5952 			return s;
5953 		},
5954 
5955 		getClasses : function() {
5956 			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
5957 
5958 			if (t.classes)
5959 				return t.classes;
5960 
5961 			function addClasses(s) {
5962 				// IE style imports
5963 				each(s.imports, function(r) {
5964 					addClasses(r);
5965 				});
5966 
5967 				each(s.cssRules || s.rules, function(r) {
5968 					// Real type or fake it on IE
5969 					switch (r.type || 1) {
5970 						// Rule
5971 						case 1:
5972 							if (r.selectorText) {
5973 								each(r.selectorText.split(','), function(v) {
5974 									v = v.replace(/^\s*|\s*$|^\s\./g, "");
5975 
5976 									// Is internal or it doesn't contain a class
5977 									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
5978 										return;
5979 
5980 									// Remove everything but class name
5981 									ov = v;
5982 									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
5983 
5984 									// Filter classes
5985 									if (f && !(v = f(v, ov)))
5986 										return;
5987 
5988 									if (!lo[v]) {
5989 										cl.push({'class' : v});
5990 										lo[v] = 1;
5991 									}
5992 								});
5993 							}
5994 							break;
5995 
5996 						// Import
5997 						case 3:
5998 							addClasses(r.styleSheet);
5999 							break;
6000 					}
6001 				});
6002 			};
6003 
6004 			try {
6005 				each(t.doc.styleSheets, addClasses);
6006 			} catch (ex) {
6007 				// Ignore
6008 			}
6009 
6010 			if (cl.length > 0)
6011 				t.classes = cl;
6012 
6013 			return cl;
6014 		},
6015 
6016 		run : function(e, f, s) {
6017 			var t = this, o;
6018 
6019 			if (t.doc && typeof(e) === 'string')
6020 				e = t.get(e);
6021 
6022 			if (!e)
6023 				return false;
6024 
6025 			s = s || this;
6026 			if (!e.nodeType && (e.length || e.length === 0)) {
6027 				o = [];
6028 
6029 				each(e, function(e, i) {
6030 					if (e) {
6031 						if (typeof(e) == 'string')
6032 							e = t.doc.getElementById(e);
6033 
6034 						o.push(f.call(s, e, i));
6035 					}
6036 				});
6037 
6038 				return o;
6039 			}
6040 
6041 			return f.call(s, e);
6042 		},
6043 
6044 		getAttribs : function(n) {
6045 			var o;
6046 
6047 			n = this.get(n);
6048 
6049 			if (!n)
6050 				return [];
6051 
6052 			if (isIE) {
6053 				o = [];
6054 
6055 				// Object will throw exception in IE
6056 				if (n.nodeName == 'OBJECT')
6057 					return n.attributes;
6058 
6059 				// IE doesn't keep the selected attribute if you clone option elements
6060 				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6061 					o.push({specified : 1, nodeName : 'selected'});
6062 
6063 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6064 				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6065 					o.push({specified : 1, nodeName : a});
6066 				});
6067 
6068 				return o;
6069 			}
6070 
6071 			return n.attributes;
6072 		},
6073 
6074 		isEmpty : function(node, elements) {
6075 			var self = this, i, attributes, type, walker, name, brCount = 0;
6076 
6077 			node = node.firstChild;
6078 			if (node) {
6079 				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6080 				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6081 
6082 				do {
6083 					type = node.nodeType;
6084 
6085 					if (type === 1) {
6086 						// Ignore bogus elements
6087 						if (node.getAttribute('data-mce-bogus'))
6088 							continue;
6089 
6090 						// Keep empty elements like <img />
6091 						name = node.nodeName.toLowerCase();
6092 						if (elements && elements[name]) {
6093 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6094 							if (name === 'br') {
6095 								brCount++;
6096 								continue;
6097 							}
6098 
6099 							return false;
6100 						}
6101 
6102 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6103 						attributes = self.getAttribs(node);
6104 						i = node.attributes.length;
6105 						while (i--) {
6106 							name = node.attributes[i].nodeName;
6107 							if (name === "name" || name === 'data-mce-bookmark')
6108 								return false;
6109 						}
6110 					}
6111 
6112 					// Keep comment nodes
6113 					if (type == 8)
6114 						return false;
6115 
6116 					// Keep non whitespace text nodes
6117 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6118 						return false;
6119 				} while (node = walker.next());
6120 			}
6121 
6122 			return brCount <= 1;
6123 		},
6124 
6125 		destroy : function(s) {
6126 			var t = this;
6127 
6128 			t.win = t.doc = t.root = t.events = t.frag = null;
6129 
6130 			// Manual destroy then remove unload handler
6131 			if (!s)
6132 				tinymce.removeUnload(t.destroy);
6133 		},
6134 
6135 		createRng : function() {
6136 			var d = this.doc;
6137 
6138 			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6139 		},
6140 
6141 		nodeIndex : function(node, normalized) {
6142 			var idx = 0, lastNodeType, lastNode, nodeType;
6143 
6144 			if (node) {
6145 				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6146 					nodeType = node.nodeType;
6147 
6148 					// Normalize text nodes
6149 					if (normalized && nodeType == 3) {
6150 						if (nodeType == lastNodeType || !node.nodeValue.length)
6151 							continue;
6152 					}
6153 					idx++;
6154 					lastNodeType = nodeType;
6155 				}
6156 			}
6157 
6158 			return idx;
6159 		},
6160 
6161 		split : function(pe, e, re) {
6162 			var t = this, r = t.createRng(), bef, aft, pa;
6163 
6164 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6165 			// but we don't want that in our code since it serves no purpose for the end user
6166 			// For example if this is chopped:
6167 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6168 			// would produce:
6169 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6170 			// this function will then trim of empty edges and produce:
6171 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6172 			function trim(node) {
6173 				var i, children = node.childNodes, type = node.nodeType;
6174 
6175 				function surroundedBySpans(node) {
6176 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6177 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6178 					return previousIsSpan && nextIsSpan;
6179 				}
6180 
6181 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6182 					return;
6183 
6184 				for (i = children.length - 1; i >= 0; i--)
6185 					trim(children[i]);
6186 
6187 				if (type != 9) {
6188 					// Keep non whitespace text nodes
6189 					if (type == 3 && node.nodeValue.length > 0) {
6190 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6191 						// Also keep text nodes with only spaces if surrounded by spans.
6192 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6193 						var trimmedLength = tinymce.trim(node.nodeValue).length;
6194 						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6195 							return;
6196 					} else if (type == 1) {
6197 						// If the only child is a bookmark then move it up
6198 						children = node.childNodes;
6199 						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6200 							node.parentNode.insertBefore(children[0], node);
6201 
6202 						// Keep non empty elements or img, hr etc
6203 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6204 							return;
6205 					}
6206 
6207 					t.remove(node);
6208 				}
6209 
6210 				return node;
6211 			};
6212 
6213 			if (pe && e) {
6214 				// Get before chunk
6215 				r.setStart(pe.parentNode, t.nodeIndex(pe));
6216 				r.setEnd(e.parentNode, t.nodeIndex(e));
6217 				bef = r.extractContents();
6218 
6219 				// Get after chunk
6220 				r = t.createRng();
6221 				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6222 				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6223 				aft = r.extractContents();
6224 
6225 				// Insert before chunk
6226 				pa = pe.parentNode;
6227 				pa.insertBefore(trim(bef), pe);
6228 
6229 				// Insert middle chunk
6230 				if (re)
6231 				pa.replaceChild(re, e);
6232 			else
6233 				pa.insertBefore(e, pe);
6234 
6235 				// Insert after chunk
6236 				pa.insertBefore(trim(aft), pe);
6237 				t.remove(pe);
6238 
6239 				return re || e;
6240 			}
6241 		},
6242 
6243 		bind : function(target, name, func, scope) {
6244 			return this.events.add(target, name, func, scope || this);
6245 		},
6246 
6247 		unbind : function(target, name, func) {
6248 			return this.events.remove(target, name, func);
6249 		},
6250 
6251 		fire : function(target, name, evt) {
6252 			return this.events.fire(target, name, evt);
6253 		},
6254 
6255 		// Returns the content editable state of a node
6256 		getContentEditable: function(node) {
6257 			var contentEditable;
6258 
6259 			// Check type
6260 			if (node.nodeType != 1) {
6261 				return null;
6262 			}
6263 
6264 			// Check for fake content editable
6265 			contentEditable = node.getAttribute("data-mce-contenteditable");
6266 			if (contentEditable && contentEditable !== "inherit") {
6267 				return contentEditable;
6268 			}
6269 
6270 			// Check for real content editable
6271 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6272 		},
6273 
6274 
6275 		_findSib : function(node, selector, name) {
6276 			var t = this, f = selector;
6277 
6278 			if (node) {
6279 				// If expression make a function of it using is
6280 				if (is(f, 'string')) {
6281 					f = function(node) {
6282 						return t.is(node, selector);
6283 					};
6284 				}
6285 
6286 				// Loop all siblings
6287 				for (node = node[name]; node; node = node[name]) {
6288 					if (f(node))
6289 						return node;
6290 				}
6291 			}
6292 
6293 			return null;
6294 		},
6295 
6296 		_isRes : function(c) {
6297 			// Is live resizble element
6298 			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6299 		}
6300 
6301 		/*
6302 		walk : function(n, f, s) {
6303 			var d = this.doc, w;
6304 
6305 			if (d.createTreeWalker) {
6306 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6307 
6308 				while ((n = w.nextNode()) != null)
6309 					f.call(s || this, n);
6310 			} else
6311 				tinymce.walk(n, f, 'childNodes', s);
6312 		}
6313 		*/
6314 
6315 		/*
6316 		toRGB : function(s) {
6317 			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6318 
6319 			if (c) {
6320 				// #FFF -> #FFFFFF
6321 				if (!is(c[3]))
6322 					c[3] = c[2] = c[1];
6323 
6324 				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6325 			}
6326 
6327 			return s;
6328 		}
6329 		*/
6330 	});
6331 
6332 	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6333 })(tinymce);
6334 
6335 (function(ns) {
6336 	// Range constructor
6337 	function Range(dom) {
6338 		var t = this,
6339 			doc = dom.doc,
6340 			EXTRACT = 0,
6341 			CLONE = 1,
6342 			DELETE = 2,
6343 			TRUE = true,
6344 			FALSE = false,
6345 			START_OFFSET = 'startOffset',
6346 			START_CONTAINER = 'startContainer',
6347 			END_CONTAINER = 'endContainer',
6348 			END_OFFSET = 'endOffset',
6349 			extend = tinymce.extend,
6350 			nodeIndex = dom.nodeIndex;
6351 
6352 		extend(t, {
6353 			// Inital states
6354 			startContainer : doc,
6355 			startOffset : 0,
6356 			endContainer : doc,
6357 			endOffset : 0,
6358 			collapsed : TRUE,
6359 			commonAncestorContainer : doc,
6360 
6361 			// Range constants
6362 			START_TO_START : 0,
6363 			START_TO_END : 1,
6364 			END_TO_END : 2,
6365 			END_TO_START : 3,
6366 
6367 			// Public methods
6368 			setStart : setStart,
6369 			setEnd : setEnd,
6370 			setStartBefore : setStartBefore,
6371 			setStartAfter : setStartAfter,
6372 			setEndBefore : setEndBefore,
6373 			setEndAfter : setEndAfter,
6374 			collapse : collapse,
6375 			selectNode : selectNode,
6376 			selectNodeContents : selectNodeContents,
6377 			compareBoundaryPoints : compareBoundaryPoints,
6378 			deleteContents : deleteContents,
6379 			extractContents : extractContents,
6380 			cloneContents : cloneContents,
6381 			insertNode : insertNode,
6382 			surroundContents : surroundContents,
6383 			cloneRange : cloneRange,
6384 			toStringIE : toStringIE
6385 		});
6386 
6387 		function createDocumentFragment() {
6388 			return doc.createDocumentFragment();
6389 		};
6390 
6391 		function setStart(n, o) {
6392 			_setEndPoint(TRUE, n, o);
6393 		};
6394 
6395 		function setEnd(n, o) {
6396 			_setEndPoint(FALSE, n, o);
6397 		};
6398 
6399 		function setStartBefore(n) {
6400 			setStart(n.parentNode, nodeIndex(n));
6401 		};
6402 
6403 		function setStartAfter(n) {
6404 			setStart(n.parentNode, nodeIndex(n) + 1);
6405 		};
6406 
6407 		function setEndBefore(n) {
6408 			setEnd(n.parentNode, nodeIndex(n));
6409 		};
6410 
6411 		function setEndAfter(n) {
6412 			setEnd(n.parentNode, nodeIndex(n) + 1);
6413 		};
6414 
6415 		function collapse(ts) {
6416 			if (ts) {
6417 				t[END_CONTAINER] = t[START_CONTAINER];
6418 				t[END_OFFSET] = t[START_OFFSET];
6419 			} else {
6420 				t[START_CONTAINER] = t[END_CONTAINER];
6421 				t[START_OFFSET] = t[END_OFFSET];
6422 			}
6423 
6424 			t.collapsed = TRUE;
6425 		};
6426 
6427 		function selectNode(n) {
6428 			setStartBefore(n);
6429 			setEndAfter(n);
6430 		};
6431 
6432 		function selectNodeContents(n) {
6433 			setStart(n, 0);
6434 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6435 		};
6436 
6437 		function compareBoundaryPoints(h, r) {
6438 			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6439 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6440 
6441 			// Check START_TO_START
6442 			if (h === 0)
6443 				return _compareBoundaryPoints(sc, so, rsc, rso);
6444 	
6445 			// Check START_TO_END
6446 			if (h === 1)
6447 				return _compareBoundaryPoints(ec, eo, rsc, rso);
6448 	
6449 			// Check END_TO_END
6450 			if (h === 2)
6451 				return _compareBoundaryPoints(ec, eo, rec, reo);
6452 	
6453 			// Check END_TO_START
6454 			if (h === 3) 
6455 				return _compareBoundaryPoints(sc, so, rec, reo);
6456 		};
6457 
6458 		function deleteContents() {
6459 			_traverse(DELETE);
6460 		};
6461 
6462 		function extractContents() {
6463 			return _traverse(EXTRACT);
6464 		};
6465 
6466 		function cloneContents() {
6467 			return _traverse(CLONE);
6468 		};
6469 
6470 		function insertNode(n) {
6471 			var startContainer = this[START_CONTAINER],
6472 				startOffset = this[START_OFFSET], nn, o;
6473 
6474 			// Node is TEXT_NODE or CDATA
6475 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6476 				if (!startOffset) {
6477 					// At the start of text
6478 					startContainer.parentNode.insertBefore(n, startContainer);
6479 				} else if (startOffset >= startContainer.nodeValue.length) {
6480 					// At the end of text
6481 					dom.insertAfter(n, startContainer);
6482 				} else {
6483 					// Middle, need to split
6484 					nn = startContainer.splitText(startOffset);
6485 					startContainer.parentNode.insertBefore(n, nn);
6486 				}
6487 			} else {
6488 				// Insert element node
6489 				if (startContainer.childNodes.length > 0)
6490 					o = startContainer.childNodes[startOffset];
6491 
6492 				if (o)
6493 					startContainer.insertBefore(n, o);
6494 				else
6495 					startContainer.appendChild(n);
6496 			}
6497 		};
6498 
6499 		function surroundContents(n) {
6500 			var f = t.extractContents();
6501 
6502 			t.insertNode(n);
6503 			n.appendChild(f);
6504 			t.selectNode(n);
6505 		};
6506 
6507 		function cloneRange() {
6508 			return extend(new Range(dom), {
6509 				startContainer : t[START_CONTAINER],
6510 				startOffset : t[START_OFFSET],
6511 				endContainer : t[END_CONTAINER],
6512 				endOffset : t[END_OFFSET],
6513 				collapsed : t.collapsed,
6514 				commonAncestorContainer : t.commonAncestorContainer
6515 			});
6516 		};
6517 
6518 		// Private methods
6519 
6520 		function _getSelectedNode(container, offset) {
6521 			var child;
6522 
6523 			if (container.nodeType == 3 /* TEXT_NODE */)
6524 				return container;
6525 
6526 			if (offset < 0)
6527 				return container;
6528 
6529 			child = container.firstChild;
6530 			while (child && offset > 0) {
6531 				--offset;
6532 				child = child.nextSibling;
6533 			}
6534 
6535 			if (child)
6536 				return child;
6537 
6538 			return container;
6539 		};
6540 
6541 		function _isCollapsed() {
6542 			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6543 		};
6544 
6545 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6546 			var c, offsetC, n, cmnRoot, childA, childB;
6547 			
6548 			// In the first case the boundary-points have the same container. A is before B
6549 			// if its offset is less than the offset of B, A is equal to B if its offset is
6550 			// equal to the offset of B, and A is after B if its offset is greater than the
6551 			// offset of B.
6552 			if (containerA == containerB) {
6553 				if (offsetA == offsetB)
6554 					return 0; // equal
6555 
6556 				if (offsetA < offsetB)
6557 					return -1; // before
6558 
6559 				return 1; // after
6560 			}
6561 
6562 			// In the second case a child node C of the container of A is an ancestor
6563 			// container of B. In this case, A is before B if the offset of A is less than or
6564 			// equal to the index of the child node C and A is after B otherwise.
6565 			c = containerB;
6566 			while (c && c.parentNode != containerA)
6567 				c = c.parentNode;
6568 
6569 			if (c) {
6570 				offsetC = 0;
6571 				n = containerA.firstChild;
6572 
6573 				while (n != c && offsetC < offsetA) {
6574 					offsetC++;
6575 					n = n.nextSibling;
6576 				}
6577 
6578 				if (offsetA <= offsetC)
6579 					return -1; // before
6580 
6581 				return 1; // after
6582 			}
6583 
6584 			// In the third case a child node C of the container of B is an ancestor container
6585 			// of A. In this case, A is before B if the index of the child node C is less than
6586 			// the offset of B and A is after B otherwise.
6587 			c = containerA;
6588 			while (c && c.parentNode != containerB) {
6589 				c = c.parentNode;
6590 			}
6591 
6592 			if (c) {
6593 				offsetC = 0;
6594 				n = containerB.firstChild;
6595 
6596 				while (n != c && offsetC < offsetB) {
6597 					offsetC++;
6598 					n = n.nextSibling;
6599 				}
6600 
6601 				if (offsetC < offsetB)
6602 					return -1; // before
6603 
6604 				return 1; // after
6605 			}
6606 
6607 			// In the fourth case, none of three other cases hold: the containers of A and B
6608 			// are siblings or descendants of sibling nodes. In this case, A is before B if
6609 			// the container of A is before the container of B in a pre-order traversal of the
6610 			// Ranges' context tree and A is after B otherwise.
6611 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6612 			childA = containerA;
6613 
6614 			while (childA && childA.parentNode != cmnRoot)
6615 				childA = childA.parentNode;
6616 
6617 			if (!childA)
6618 				childA = cmnRoot;
6619 
6620 			childB = containerB;
6621 			while (childB && childB.parentNode != cmnRoot)
6622 				childB = childB.parentNode;
6623 
6624 			if (!childB)
6625 				childB = cmnRoot;
6626 
6627 			if (childA == childB)
6628 				return 0; // equal
6629 
6630 			n = cmnRoot.firstChild;
6631 			while (n) {
6632 				if (n == childA)
6633 					return -1; // before
6634 
6635 				if (n == childB)
6636 					return 1; // after
6637 
6638 				n = n.nextSibling;
6639 			}
6640 		};
6641 
6642 		function _setEndPoint(st, n, o) {
6643 			var ec, sc;
6644 
6645 			if (st) {
6646 				t[START_CONTAINER] = n;
6647 				t[START_OFFSET] = o;
6648 			} else {
6649 				t[END_CONTAINER] = n;
6650 				t[END_OFFSET] = o;
6651 			}
6652 
6653 			// If one boundary-point of a Range is set to have a root container
6654 			// other than the current one for the Range, the Range is collapsed to
6655 			// the new position. This enforces the restriction that both boundary-
6656 			// points of a Range must have the same root container.
6657 			ec = t[END_CONTAINER];
6658 			while (ec.parentNode)
6659 				ec = ec.parentNode;
6660 
6661 			sc = t[START_CONTAINER];
6662 			while (sc.parentNode)
6663 				sc = sc.parentNode;
6664 
6665 			if (sc == ec) {
6666 				// The start position of a Range is guaranteed to never be after the
6667 				// end position. To enforce this restriction, if the start is set to
6668 				// be at a position after the end, the Range is collapsed to that
6669 				// position.
6670 				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6671 					t.collapse(st);
6672 			} else
6673 				t.collapse(st);
6674 
6675 			t.collapsed = _isCollapsed();
6676 			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6677 		};
6678 
6679 		function _traverse(how) {
6680 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6681 
6682 			if (t[START_CONTAINER] == t[END_CONTAINER])
6683 				return _traverseSameContainer(how);
6684 
6685 			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6686 				if (p == t[START_CONTAINER])
6687 					return _traverseCommonStartContainer(c, how);
6688 
6689 				++endContainerDepth;
6690 			}
6691 
6692 			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6693 				if (p == t[END_CONTAINER])
6694 					return _traverseCommonEndContainer(c, how);
6695 
6696 				++startContainerDepth;
6697 			}
6698 
6699 			depthDiff = startContainerDepth - endContainerDepth;
6700 
6701 			startNode = t[START_CONTAINER];
6702 			while (depthDiff > 0) {
6703 				startNode = startNode.parentNode;
6704 				depthDiff--;
6705 			}
6706 
6707 			endNode = t[END_CONTAINER];
6708 			while (depthDiff < 0) {
6709 				endNode = endNode.parentNode;
6710 				depthDiff++;
6711 			}
6712 
6713 			// ascend the ancestor hierarchy until we have a common parent.
6714 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6715 				startNode = sp;
6716 				endNode = ep;
6717 			}
6718 
6719 			return _traverseCommonAncestors(startNode, endNode, how);
6720 		};
6721 
6722 		 function _traverseSameContainer(how) {
6723 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6724 
6725 			if (how != DELETE)
6726 				frag = createDocumentFragment();
6727 
6728 			// If selection is empty, just return the fragment
6729 			if (t[START_OFFSET] == t[END_OFFSET])
6730 				return frag;
6731 
6732 			// Text node needs special case handling
6733 			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6734 				// get the substring
6735 				s = t[START_CONTAINER].nodeValue;
6736 				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6737 
6738 				// set the original text node to its new value
6739 				if (how != CLONE) {
6740 					n = t[START_CONTAINER];
6741 					start = t[START_OFFSET];
6742 					len = t[END_OFFSET] - t[START_OFFSET];
6743 
6744 					if (start === 0 && len >= n.nodeValue.length - 1) {
6745 						n.parentNode.removeChild(n);
6746 					} else {
6747 						n.deleteData(start, len);
6748 					}
6749 
6750 					// Nothing is partially selected, so collapse to start point
6751 					t.collapse(TRUE);
6752 				}
6753 
6754 				if (how == DELETE)
6755 					return;
6756 
6757 				if (sub.length > 0) {
6758 					frag.appendChild(doc.createTextNode(sub));
6759 				}
6760 
6761 				return frag;
6762 			}
6763 
6764 			// Copy nodes between the start/end offsets.
6765 			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6766 			cnt = t[END_OFFSET] - t[START_OFFSET];
6767 
6768 			while (n && cnt > 0) {
6769 				sibling = n.nextSibling;
6770 				xferNode = _traverseFullySelected(n, how);
6771 
6772 				if (frag)
6773 					frag.appendChild( xferNode );
6774 
6775 				--cnt;
6776 				n = sibling;
6777 			}
6778 
6779 			// Nothing is partially selected, so collapse to start point
6780 			if (how != CLONE)
6781 				t.collapse(TRUE);
6782 
6783 			return frag;
6784 		};
6785 
6786 		function _traverseCommonStartContainer(endAncestor, how) {
6787 			var frag, n, endIdx, cnt, sibling, xferNode;
6788 
6789 			if (how != DELETE)
6790 				frag = createDocumentFragment();
6791 
6792 			n = _traverseRightBoundary(endAncestor, how);
6793 
6794 			if (frag)
6795 				frag.appendChild(n);
6796 
6797 			endIdx = nodeIndex(endAncestor);
6798 			cnt = endIdx - t[START_OFFSET];
6799 
6800 			if (cnt <= 0) {
6801 				// Collapse to just before the endAncestor, which
6802 				// is partially selected.
6803 				if (how != CLONE) {
6804 					t.setEndBefore(endAncestor);
6805 					t.collapse(FALSE);
6806 				}
6807 
6808 				return frag;
6809 			}
6810 
6811 			n = endAncestor.previousSibling;
6812 			while (cnt > 0) {
6813 				sibling = n.previousSibling;
6814 				xferNode = _traverseFullySelected(n, how);
6815 
6816 				if (frag)
6817 					frag.insertBefore(xferNode, frag.firstChild);
6818 
6819 				--cnt;
6820 				n = sibling;
6821 			}
6822 
6823 			// Collapse to just before the endAncestor, which
6824 			// is partially selected.
6825 			if (how != CLONE) {
6826 				t.setEndBefore(endAncestor);
6827 				t.collapse(FALSE);
6828 			}
6829 
6830 			return frag;
6831 		};
6832 
6833 		function _traverseCommonEndContainer(startAncestor, how) {
6834 			var frag, startIdx, n, cnt, sibling, xferNode;
6835 
6836 			if (how != DELETE)
6837 				frag = createDocumentFragment();
6838 
6839 			n = _traverseLeftBoundary(startAncestor, how);
6840 			if (frag)
6841 				frag.appendChild(n);
6842 
6843 			startIdx = nodeIndex(startAncestor);
6844 			++startIdx; // Because we already traversed it
6845 
6846 			cnt = t[END_OFFSET] - startIdx;
6847 			n = startAncestor.nextSibling;
6848 			while (n && cnt > 0) {
6849 				sibling = n.nextSibling;
6850 				xferNode = _traverseFullySelected(n, how);
6851 
6852 				if (frag)
6853 					frag.appendChild(xferNode);
6854 
6855 				--cnt;
6856 				n = sibling;
6857 			}
6858 
6859 			if (how != CLONE) {
6860 				t.setStartAfter(startAncestor);
6861 				t.collapse(TRUE);
6862 			}
6863 
6864 			return frag;
6865 		};
6866 
6867 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
6868 			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
6869 
6870 			if (how != DELETE)
6871 				frag = createDocumentFragment();
6872 
6873 			n = _traverseLeftBoundary(startAncestor, how);
6874 			if (frag)
6875 				frag.appendChild(n);
6876 
6877 			commonParent = startAncestor.parentNode;
6878 			startOffset = nodeIndex(startAncestor);
6879 			endOffset = nodeIndex(endAncestor);
6880 			++startOffset;
6881 
6882 			cnt = endOffset - startOffset;
6883 			sibling = startAncestor.nextSibling;
6884 
6885 			while (cnt > 0) {
6886 				nextSibling = sibling.nextSibling;
6887 				n = _traverseFullySelected(sibling, how);
6888 
6889 				if (frag)
6890 					frag.appendChild(n);
6891 
6892 				sibling = nextSibling;
6893 				--cnt;
6894 			}
6895 
6896 			n = _traverseRightBoundary(endAncestor, how);
6897 
6898 			if (frag)
6899 				frag.appendChild(n);
6900 
6901 			if (how != CLONE) {
6902 				t.setStartAfter(startAncestor);
6903 				t.collapse(TRUE);
6904 			}
6905 
6906 			return frag;
6907 		};
6908 
6909 		function _traverseRightBoundary(root, how) {
6910 			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
6911 
6912 			if (next == root)
6913 				return _traverseNode(next, isFullySelected, FALSE, how);
6914 
6915 			parent = next.parentNode;
6916 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
6917 
6918 			while (parent) {
6919 				while (next) {
6920 					prevSibling = next.previousSibling;
6921 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
6922 
6923 					if (how != DELETE)
6924 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
6925 
6926 					isFullySelected = TRUE;
6927 					next = prevSibling;
6928 				}
6929 
6930 				if (parent == root)
6931 					return clonedParent;
6932 
6933 				next = parent.previousSibling;
6934 				parent = parent.parentNode;
6935 
6936 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
6937 
6938 				if (how != DELETE)
6939 					clonedGrandParent.appendChild(clonedParent);
6940 
6941 				clonedParent = clonedGrandParent;
6942 			}
6943 		};
6944 
6945 		function _traverseLeftBoundary(root, how) {
6946 			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
6947 
6948 			if (next == root)
6949 				return _traverseNode(next, isFullySelected, TRUE, how);
6950 
6951 			parent = next.parentNode;
6952 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
6953 
6954 			while (parent) {
6955 				while (next) {
6956 					nextSibling = next.nextSibling;
6957 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
6958 
6959 					if (how != DELETE)
6960 						clonedParent.appendChild(clonedChild);
6961 
6962 					isFullySelected = TRUE;
6963 					next = nextSibling;
6964 				}
6965 
6966 				if (parent == root)
6967 					return clonedParent;
6968 
6969 				next = parent.nextSibling;
6970 				parent = parent.parentNode;
6971 
6972 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
6973 
6974 				if (how != DELETE)
6975 					clonedGrandParent.appendChild(clonedParent);
6976 
6977 				clonedParent = clonedGrandParent;
6978 			}
6979 		};
6980 
6981 		function _traverseNode(n, isFullySelected, isLeft, how) {
6982 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
6983 
6984 			if (isFullySelected)
6985 				return _traverseFullySelected(n, how);
6986 
6987 			if (n.nodeType == 3 /* TEXT_NODE */) {
6988 				txtValue = n.nodeValue;
6989 
6990 				if (isLeft) {
6991 					offset = t[START_OFFSET];
6992 					newNodeValue = txtValue.substring(offset);
6993 					oldNodeValue = txtValue.substring(0, offset);
6994 				} else {
6995 					offset = t[END_OFFSET];
6996 					newNodeValue = txtValue.substring(0, offset);
6997 					oldNodeValue = txtValue.substring(offset);
6998 				}
6999 
7000 				if (how != CLONE)
7001 					n.nodeValue = oldNodeValue;
7002 
7003 				if (how == DELETE)
7004 					return;
7005 
7006 				newNode = dom.clone(n, FALSE);
7007 				newNode.nodeValue = newNodeValue;
7008 
7009 				return newNode;
7010 			}
7011 
7012 			if (how == DELETE)
7013 				return;
7014 
7015 			return dom.clone(n, FALSE);
7016 		};
7017 
7018 		function _traverseFullySelected(n, how) {
7019 			if (how != DELETE)
7020 				return how == CLONE ? dom.clone(n, TRUE) : n;
7021 
7022 			n.parentNode.removeChild(n);
7023 		};
7024 
7025 		function toStringIE() {
7026 			return dom.create('body', null, cloneContents()).outerText;
7027 		}
7028 		
7029 		return t;
7030 	};
7031 
7032 	ns.Range = Range;
7033 
7034 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7035 	Range.prototype.toString = function() {
7036 		return this.toStringIE();
7037 	};
7038 })(tinymce.dom);
7039 
7040 (function() {
7041 	function Selection(selection) {
7042 		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7043 
7044 		function getPosition(rng, start) {
7045 			var checkRng, startIndex = 0, endIndex, inside,
7046 				children, child, offset, index, position = -1, parent;
7047 
7048 			// Setup test range, collapse it and get the parent
7049 			checkRng = rng.duplicate();
7050 			checkRng.collapse(start);
7051 			parent = checkRng.parentElement();
7052 
7053 			// Check if the selection is within the right document
7054 			if (parent.ownerDocument !== selection.dom.doc)
7055 				return;
7056 
7057 			// IE will report non editable elements as it's parent so look for an editable one
7058 			while (parent.contentEditable === "false") {
7059 				parent = parent.parentNode;
7060 			}
7061 
7062 			// If parent doesn't have any children then return that we are inside the element
7063 			if (!parent.hasChildNodes()) {
7064 				return {node : parent, inside : 1};
7065 			}
7066 
7067 			// Setup node list and endIndex
7068 			children = parent.children;
7069 			endIndex = children.length - 1;
7070 
7071 			// Perform a binary search for the position
7072 			while (startIndex <= endIndex) {
7073 				index = Math.floor((startIndex + endIndex) / 2);
7074 
7075 				// Move selection to node and compare the ranges
7076 				child = children[index];
7077 				checkRng.moveToElementText(child);
7078 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7079 
7080 				// Before/after or an exact match
7081 				if (position > 0) {
7082 					endIndex = index - 1;
7083 				} else if (position < 0) {
7084 					startIndex = index + 1;
7085 				} else {
7086 					return {node : child};
7087 				}
7088 			}
7089 
7090 			// Check if child position is before or we didn't find a position
7091 			if (position < 0) {
7092 				// No element child was found use the parent element and the offset inside that
7093 				if (!child) {
7094 					checkRng.moveToElementText(parent);
7095 					checkRng.collapse(true);
7096 					child = parent;
7097 					inside = true;
7098 				} else
7099 					checkRng.collapse(false);
7100 
7101 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7102 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7103 				offset = 0;
7104 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7105 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7106 						break;
7107 					}
7108 
7109 					offset++;
7110 				}
7111 			} else {
7112 				// Child position is after the selection endpoint
7113 				checkRng.collapse(true);
7114 
7115 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7116 				offset = 0;
7117 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7118 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7119 						break;
7120 					}
7121 
7122 					offset++;
7123 				}
7124 			}
7125 
7126 			return {node : child, position : position, offset : offset, inside : inside};
7127 		};
7128 
7129 		// Returns a W3C DOM compatible range object by using the IE Range API
7130 		function getRange() {
7131 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7132 
7133 			// If selection is outside the current document just return an empty range
7134 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7135 			if (element.ownerDocument != dom.doc)
7136 				return domRange;
7137 
7138 			collapsed = selection.isCollapsed();
7139 
7140 			// Handle control selection
7141 			if (ieRange.item) {
7142 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7143 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7144 
7145 				return domRange;
7146 			}
7147 
7148 			function findEndPoint(start) {
7149 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7150 
7151 				container = endPoint.node;
7152 				offset = endPoint.offset;
7153 
7154 				if (endPoint.inside && !container.hasChildNodes()) {
7155 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7156 					return;
7157 				}
7158 
7159 				if (offset === undef) {
7160 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7161 					return;
7162 				}
7163 
7164 				if (endPoint.position < 0) {
7165 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7166 
7167 					if (!sibling) {
7168 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7169 						return;
7170 					}
7171 
7172 					if (!offset) {
7173 						if (sibling.nodeType == 3)
7174 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7175 						else
7176 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7177 
7178 						return;
7179 					}
7180 
7181 					// Find the text node and offset
7182 					while (sibling) {
7183 						nodeValue = sibling.nodeValue;
7184 						textNodeOffset += nodeValue.length;
7185 
7186 						// We are at or passed the position we where looking for
7187 						if (textNodeOffset >= offset) {
7188 							container = sibling;
7189 							textNodeOffset -= offset;
7190 							textNodeOffset = nodeValue.length - textNodeOffset;
7191 							break;
7192 						}
7193 
7194 						sibling = sibling.nextSibling;
7195 					}
7196 				} else {
7197 					// Find the text node and offset
7198 					sibling = container.previousSibling;
7199 
7200 					if (!sibling)
7201 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7202 
7203 					// If there isn't any text to loop then use the first position
7204 					if (!offset) {
7205 						if (container.nodeType == 3)
7206 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7207 						else
7208 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7209 
7210 						return;
7211 					}
7212 
7213 					while (sibling) {
7214 						textNodeOffset += sibling.nodeValue.length;
7215 
7216 						// We are at or passed the position we where looking for
7217 						if (textNodeOffset >= offset) {
7218 							container = sibling;
7219 							textNodeOffset -= offset;
7220 							break;
7221 						}
7222 
7223 						sibling = sibling.previousSibling;
7224 					}
7225 				}
7226 
7227 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7228 			};
7229 
7230 			try {
7231 				// Find start point
7232 				findEndPoint(true);
7233 
7234 				// Find end point if needed
7235 				if (!collapsed)
7236 					findEndPoint();
7237 			} catch (ex) {
7238 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7239 				// access the nodeValue or other properties of text nodes. This seems to happend when
7240 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7241 				if (ex.number == -2147024809) {
7242 					// Get the current selection
7243 					bookmark = self.getBookmark(2);
7244 
7245 					// Get start element
7246 					tmpRange = ieRange.duplicate();
7247 					tmpRange.collapse(true);
7248 					element = tmpRange.parentElement();
7249 
7250 					// Get end element
7251 					if (!collapsed) {
7252 						tmpRange = ieRange.duplicate();
7253 						tmpRange.collapse(false);
7254 						element2 = tmpRange.parentElement();
7255 						element2.innerHTML = element2.innerHTML;
7256 					}
7257 
7258 					// Remove the broken elements
7259 					element.innerHTML = element.innerHTML;
7260 
7261 					// Restore the selection
7262 					self.moveToBookmark(bookmark);
7263 
7264 					// Since the range has moved we need to re-get it
7265 					ieRange = selection.getRng();
7266 
7267 					// Find start point
7268 					findEndPoint(true);
7269 
7270 					// Find end point if needed
7271 					if (!collapsed)
7272 						findEndPoint();
7273 				} else
7274 					throw ex; // Throw other errors
7275 			}
7276 
7277 			return domRange;
7278 		};
7279 
7280 		this.getBookmark = function(type) {
7281 			var rng = selection.getRng(), start, end, bookmark = {};
7282 
7283 			function getIndexes(node) {
7284 				var parent, root, children, i, indexes = [];
7285 
7286 				parent = node.parentNode;
7287 				root = dom.getRoot().parentNode;
7288 
7289 				while (parent != root && parent.nodeType !== 9) {
7290 					children = parent.children;
7291 
7292 					i = children.length;
7293 					while (i--) {
7294 						if (node === children[i]) {
7295 							indexes.push(i);
7296 							break;
7297 						}
7298 					}
7299 
7300 					node = parent;
7301 					parent = parent.parentNode;
7302 				}
7303 
7304 				return indexes;
7305 			};
7306 
7307 			function getBookmarkEndPoint(start) {
7308 				var position;
7309 
7310 				position = getPosition(rng, start);
7311 				if (position) {
7312 					return {
7313 						position : position.position,
7314 						offset : position.offset,
7315 						indexes : getIndexes(position.node),
7316 						inside : position.inside
7317 					};
7318 				}
7319 			};
7320 
7321 			// Non ubstructive bookmark
7322 			if (type === 2) {
7323 				// Handle text selection
7324 				if (!rng.item) {
7325 					bookmark.start = getBookmarkEndPoint(true);
7326 
7327 					if (!selection.isCollapsed())
7328 						bookmark.end = getBookmarkEndPoint();
7329 				} else
7330 					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7331 			}
7332 
7333 			return bookmark;
7334 		};
7335 
7336 		this.moveToBookmark = function(bookmark) {
7337 			var rng, body = dom.doc.body;
7338 
7339 			function resolveIndexes(indexes) {
7340 				var node, i, idx, children;
7341 
7342 				node = dom.getRoot();
7343 				for (i = indexes.length - 1; i >= 0; i--) {
7344 					children = node.children;
7345 					idx = indexes[i];
7346 
7347 					if (idx <= children.length - 1) {
7348 						node = children[idx];
7349 					}
7350 				}
7351 
7352 				return node;
7353 			};
7354 			
7355 			function setBookmarkEndPoint(start) {
7356 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7357 
7358 				if (endPoint) {
7359 					moveLeft = endPoint.position > 0;
7360 
7361 					moveRng = body.createTextRange();
7362 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7363 
7364 					offset = endPoint.offset;
7365 					if (offset !== undef) {
7366 						moveRng.collapse(endPoint.inside || moveLeft);
7367 						moveRng.moveStart('character', moveLeft ? -offset : offset);
7368 					} else
7369 						moveRng.collapse(start);
7370 
7371 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7372 
7373 					if (start)
7374 						rng.collapse(true);
7375 				}
7376 			};
7377 
7378 			if (bookmark.start) {
7379 				if (bookmark.start.ctrl) {
7380 					rng = body.createControlRange();
7381 					rng.addElement(resolveIndexes(bookmark.start.indexes));
7382 					rng.select();
7383 				} else {
7384 					rng = body.createTextRange();
7385 					setBookmarkEndPoint(true);
7386 					setBookmarkEndPoint();
7387 					rng.select();
7388 				}
7389 			}
7390 		};
7391 
7392 		this.addRange = function(rng) {
7393 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
7394 
7395 			function setEndPoint(start) {
7396 				var container, offset, marker, tmpRng, nodes;
7397 
7398 				marker = dom.create('a');
7399 				container = start ? startContainer : endContainer;
7400 				offset = start ? startOffset : endOffset;
7401 				tmpRng = ieRng.duplicate();
7402 
7403 				if (container == doc || container == doc.documentElement) {
7404 					container = body;
7405 					offset = 0;
7406 				}
7407 
7408 				if (container.nodeType == 3) {
7409 					container.parentNode.insertBefore(marker, container);
7410 					tmpRng.moveToElementText(marker);
7411 					tmpRng.moveStart('character', offset);
7412 					dom.remove(marker);
7413 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7414 				} else {
7415 					nodes = container.childNodes;
7416 
7417 					if (nodes.length) {
7418 						if (offset >= nodes.length) {
7419 							dom.insertAfter(marker, nodes[nodes.length - 1]);
7420 						} else {
7421 							container.insertBefore(marker, nodes[offset]);
7422 						}
7423 
7424 						tmpRng.moveToElementText(marker);
7425 					} else if (container.canHaveHTML) {
7426 						// Empty node selection for example <div>|</div>
7427 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7428 						container.innerHTML = '<span>\uFEFF</span>';
7429 						marker = container.firstChild;
7430 						tmpRng.moveToElementText(marker);
7431 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7432 					}
7433 
7434 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7435 					dom.remove(marker);
7436 				}
7437 			}
7438 
7439 			// Setup some shorter versions
7440 			startContainer = rng.startContainer;
7441 			startOffset = rng.startOffset;
7442 			endContainer = rng.endContainer;
7443 			endOffset = rng.endOffset;
7444 			ieRng = body.createTextRange();
7445 
7446 			// If single element selection then try making a control selection out of it
7447 			if (startContainer == endContainer && startContainer.nodeType == 1) {
7448 				// Trick to place the caret inside an empty block element like <p></p>
7449 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7450 					if (startContainer.canHaveHTML) {
7451 						// Check if previous sibling is an empty block if it is then we need to render it
7452 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7453 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7454 						sibling = startContainer.previousSibling;
7455 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7456 							sibling.innerHTML = '\uFEFF';
7457 						} else {
7458 							sibling = null;
7459 						}
7460 
7461 						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7462 						ieRng.moveToElementText(startContainer.lastChild);
7463 						ieRng.select();
7464 						dom.doc.selection.clear();
7465 						startContainer.innerHTML = '';
7466 
7467 						if (sibling) {
7468 							sibling.innerHTML = '';
7469 						}
7470 						return;
7471 					} else {
7472 						startOffset = dom.nodeIndex(startContainer);
7473 						startContainer = startContainer.parentNode;
7474 					}
7475 				}
7476 
7477 				if (startOffset == endOffset - 1) {
7478 					try {
7479 						ctrlRng = body.createControlRange();
7480 						ctrlRng.addElement(startContainer.childNodes[startOffset]);
7481 						ctrlRng.select();
7482 						return;
7483 					} catch (ex) {
7484 						// Ignore
7485 					}
7486 				}
7487 			}
7488 
7489 			// Set start/end point of selection
7490 			setEndPoint(true);
7491 			setEndPoint();
7492 
7493 			// Select the new range and scroll it into view
7494 			ieRng.select();
7495 		};
7496 
7497 		// Expose range method
7498 		this.getRangeAt = getRange;
7499 	};
7500 
7501 	// Expose the selection object
7502 	tinymce.dom.TridentSelection = Selection;
7503 })();
7504 
7505 
7506 /*
7507  * Sizzle CSS Selector Engine
7508  *  Copyright, The Dojo Foundation
7509  *  Released under the MIT, BSD, and GPL Licenses.
7510  *  More information: http://sizzlejs.com/
7511  */
7512 (function(){
7513 
7514 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
7515 	expando = "sizcache",
7516 	done = 0,
7517 	toString = Object.prototype.toString,
7518 	hasDuplicate = false,
7519 	baseHasDuplicate = true,
7520 	rBackslash = /\\/g,
7521 	rReturn = /\r\n/g,
7522 	rNonWord = /\W/;
7523 
7524 // Here we check if the JavaScript engine is using some sort of
7525 // optimization where it does not always call our comparision
7526 // function. If that is the case, discard the hasDuplicate value.
7527 //   Thus far that includes Google Chrome.
7528 [0, 0].sort(function() {
7529 	baseHasDuplicate = false;
7530 	return 0;
7531 });
7532 
7533 var Sizzle = function( selector, context, results, seed ) {
7534 	results = results || [];
7535 	context = context || document;
7536 
7537 	var origContext = context;
7538 
7539 	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
7540 		return [];
7541 	}
7542 
7543 	if ( !selector || typeof selector !== "string" ) {
7544 		return results;
7545 	}
7546 
7547 	var m, set, checkSet, extra, ret, cur, pop, i,
7548 		prune = true,
7549 		contextXML = Sizzle.isXML( context ),
7550 		parts = [],
7551 		soFar = selector;
7552 
7553 	// Reset the position of the chunker regexp (start from head)
7554 	do {
7555 		chunker.exec( "" );
7556 		m = chunker.exec( soFar );
7557 
7558 		if ( m ) {
7559 			soFar = m[3];
7560 
7561 			parts.push( m[1] );
7562 
7563 			if ( m[2] ) {
7564 				extra = m[3];
7565 				break;
7566 			}
7567 		}
7568 	} while ( m );
7569 
7570 	if ( parts.length > 1 && origPOS.exec( selector ) ) {
7571 
7572 		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
7573 			set = posProcess( parts[0] + parts[1], context, seed );
7574 
7575 		} else {
7576 			set = Expr.relative[ parts[0] ] ?
7577 				[ context ] :
7578 				Sizzle( parts.shift(), context );
7579 
7580 			while ( parts.length ) {
7581 				selector = parts.shift();
7582 
7583 				if ( Expr.relative[ selector ] ) {
7584 					selector += parts.shift();
7585 				}
7586 
7587 				set = posProcess( selector, set, seed );
7588 			}
7589 		}
7590 
7591 	} else {
7592 		// Take a shortcut and set the context if the root selector is an ID
7593 		// (but not if it'll be faster if the inner selector is an ID)
7594 		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
7595 				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
7596 
7597 			ret = Sizzle.find( parts.shift(), context, contextXML );
7598 			context = ret.expr ?
7599 				Sizzle.filter( ret.expr, ret.set )[0] :
7600 				ret.set[0];
7601 		}
7602 
7603 		if ( context ) {
7604 			ret = seed ?
7605 				{ expr: parts.pop(), set: makeArray(seed) } :
7606 				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
7607 
7608 			set = ret.expr ?
7609 				Sizzle.filter( ret.expr, ret.set ) :
7610 				ret.set;
7611 
7612 			if ( parts.length > 0 ) {
7613 				checkSet = makeArray( set );
7614 
7615 			} else {
7616 				prune = false;
7617 			}
7618 
7619 			while ( parts.length ) {
7620 				cur = parts.pop();
7621 				pop = cur;
7622 
7623 				if ( !Expr.relative[ cur ] ) {
7624 					cur = "";
7625 				} else {
7626 					pop = parts.pop();
7627 				}
7628 
7629 				if ( pop == null ) {
7630 					pop = context;
7631 				}
7632 
7633 				Expr.relative[ cur ]( checkSet, pop, contextXML );
7634 			}
7635 
7636 		} else {
7637 			checkSet = parts = [];
7638 		}
7639 	}
7640 
7641 	if ( !checkSet ) {
7642 		checkSet = set;
7643 	}
7644 
7645 	if ( !checkSet ) {
7646 		Sizzle.error( cur || selector );
7647 	}
7648 
7649 	if ( toString.call(checkSet) === "[object Array]" ) {
7650 		if ( !prune ) {
7651 			results.push.apply( results, checkSet );
7652 
7653 		} else if ( context && context.nodeType === 1 ) {
7654 			for ( i = 0; checkSet[i] != null; i++ ) {
7655 				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
7656 					results.push( set[i] );
7657 				}
7658 			}
7659 
7660 		} else {
7661 			for ( i = 0; checkSet[i] != null; i++ ) {
7662 				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
7663 					results.push( set[i] );
7664 				}
7665 			}
7666 		}
7667 
7668 	} else {
7669 		makeArray( checkSet, results );
7670 	}
7671 
7672 	if ( extra ) {
7673 		Sizzle( extra, origContext, results, seed );
7674 		Sizzle.uniqueSort( results );
7675 	}
7676 
7677 	return results;
7678 };
7679 
7680 Sizzle.uniqueSort = function( results ) {
7681 	if ( sortOrder ) {
7682 		hasDuplicate = baseHasDuplicate;
7683 		results.sort( sortOrder );
7684 
7685 		if ( hasDuplicate ) {
7686 			for ( var i = 1; i < results.length; i++ ) {
7687 				if ( results[i] === results[ i - 1 ] ) {
7688 					results.splice( i--, 1 );
7689 				}
7690 			}
7691 		}
7692 	}
7693 
7694 	return results;
7695 };
7696 
7697 Sizzle.matches = function( expr, set ) {
7698 	return Sizzle( expr, null, null, set );
7699 };
7700 
7701 Sizzle.matchesSelector = function( node, expr ) {
7702 	return Sizzle( expr, null, null, [node] ).length > 0;
7703 };
7704 
7705 Sizzle.find = function( expr, context, isXML ) {
7706 	var set, i, len, match, type, left;
7707 
7708 	if ( !expr ) {
7709 		return [];
7710 	}
7711 
7712 	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
7713 		type = Expr.order[i];
7714 
7715 		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
7716 			left = match[1];
7717 			match.splice( 1, 1 );
7718 
7719 			if ( left.substr( left.length - 1 ) !== "\\" ) {
7720 				match[1] = (match[1] || "").replace( rBackslash, "" );
7721 				set = Expr.find[ type ]( match, context, isXML );
7722 
7723 				if ( set != null ) {
7724 					expr = expr.replace( Expr.match[ type ], "" );
7725 					break;
7726 				}
7727 			}
7728 		}
7729 	}
7730 
7731 	if ( !set ) {
7732 		set = typeof context.getElementsByTagName !== "undefined" ?
7733 			context.getElementsByTagName( "*" ) :
7734 			[];
7735 	}
7736 
7737 	return { set: set, expr: expr };
7738 };
7739 
7740 Sizzle.filter = function( expr, set, inplace, not ) {
7741 	var match, anyFound,
7742 		type, found, item, filter, left,
7743 		i, pass,
7744 		old = expr,
7745 		result = [],
7746 		curLoop = set,
7747 		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
7748 
7749 	while ( expr && set.length ) {
7750 		for ( type in Expr.filter ) {
7751 			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
7752 				filter = Expr.filter[ type ];
7753 				left = match[1];
7754 
7755 				anyFound = false;
7756 
7757 				match.splice(1,1);
7758 
7759 				if ( left.substr( left.length - 1 ) === "\\" ) {
7760 					continue;
7761 				}
7762 
7763 				if ( curLoop === result ) {
7764 					result = [];
7765 				}
7766 
7767 				if ( Expr.preFilter[ type ] ) {
7768 					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
7769 
7770 					if ( !match ) {
7771 						anyFound = found = true;
7772 
7773 					} else if ( match === true ) {
7774 						continue;
7775 					}
7776 				}
7777 
7778 				if ( match ) {
7779 					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
7780 						if ( item ) {
7781 							found = filter( item, match, i, curLoop );
7782 							pass = not ^ found;
7783 
7784 							if ( inplace && found != null ) {
7785 								if ( pass ) {
7786 									anyFound = true;
7787 
7788 								} else {
7789 									curLoop[i] = false;
7790 								}
7791 
7792 							} else if ( pass ) {
7793 								result.push( item );
7794 								anyFound = true;
7795 							}
7796 						}
7797 					}
7798 				}
7799 
7800 				if ( found !== undefined ) {
7801 					if ( !inplace ) {
7802 						curLoop = result;
7803 					}
7804 
7805 					expr = expr.replace( Expr.match[ type ], "" );
7806 
7807 					if ( !anyFound ) {
7808 						return [];
7809 					}
7810 
7811 					break;
7812 				}
7813 			}
7814 		}
7815 
7816 		// Improper expression
7817 		if ( expr === old ) {
7818 			if ( anyFound == null ) {
7819 				Sizzle.error( expr );
7820 
7821 			} else {
7822 				break;
7823 			}
7824 		}
7825 
7826 		old = expr;
7827 	}
7828 
7829 	return curLoop;
7830 };
7831 
7832 Sizzle.error = function( msg ) {
7833 	throw new Error( "Syntax error, unrecognized expression: " + msg );
7834 };
7835 
7836 var getText = Sizzle.getText = function( elem ) {
7837     var i, node,
7838 		nodeType = elem.nodeType,
7839 		ret = "";
7840 
7841 	if ( nodeType ) {
7842 		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
7843 			// Use textContent || innerText for elements
7844 			if ( typeof elem.textContent === 'string' ) {
7845 				return elem.textContent;
7846 			} else if ( typeof elem.innerText === 'string' ) {
7847 				// Replace IE's carriage returns
7848 				return elem.innerText.replace( rReturn, '' );
7849 			} else {
7850 				// Traverse it's children
7851 				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
7852 					ret += getText( elem );
7853 				}
7854 			}
7855 		} else if ( nodeType === 3 || nodeType === 4 ) {
7856 			return elem.nodeValue;
7857 		}
7858 	} else {
7859 
7860 		// If no nodeType, this is expected to be an array
7861 		for ( i = 0; (node = elem[i]); i++ ) {
7862 			// Do not traverse comment nodes
7863 			if ( node.nodeType !== 8 ) {
7864 				ret += getText( node );
7865 			}
7866 		}
7867 	}
7868 	return ret;
7869 };
7870 
7871 var Expr = Sizzle.selectors = {
7872 	order: [ "ID", "NAME", "TAG" ],
7873 
7874 	match: {
7875 		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7876 		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7877 		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
7878 		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
7879 		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
7880 		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
7881 		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
7882 		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
7883 	},
7884 
7885 	leftMatch: {},
7886 
7887 	attrMap: {
7888 		"class": "className",
7889 		"for": "htmlFor"
7890 	},
7891 
7892 	attrHandle: {
7893 		href: function( elem ) {
7894 			return elem.getAttribute( "href" );
7895 		},
7896 		type: function( elem ) {
7897 			return elem.getAttribute( "type" );
7898 		}
7899 	},
7900 
7901 	relative: {
7902 		"+": function(checkSet, part){
7903 			var isPartStr = typeof part === "string",
7904 				isTag = isPartStr && !rNonWord.test( part ),
7905 				isPartStrNotTag = isPartStr && !isTag;
7906 
7907 			if ( isTag ) {
7908 				part = part.toLowerCase();
7909 			}
7910 
7911 			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
7912 				if ( (elem = checkSet[i]) ) {
7913 					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
7914 
7915 					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
7916 						elem || false :
7917 						elem === part;
7918 				}
7919 			}
7920 
7921 			if ( isPartStrNotTag ) {
7922 				Sizzle.filter( part, checkSet, true );
7923 			}
7924 		},
7925 
7926 		">": function( checkSet, part ) {
7927 			var elem,
7928 				isPartStr = typeof part === "string",
7929 				i = 0,
7930 				l = checkSet.length;
7931 
7932 			if ( isPartStr && !rNonWord.test( part ) ) {
7933 				part = part.toLowerCase();
7934 
7935 				for ( ; i < l; i++ ) {
7936 					elem = checkSet[i];
7937 
7938 					if ( elem ) {
7939 						var parent = elem.parentNode;
7940 						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
7941 					}
7942 				}
7943 
7944 			} else {
7945 				for ( ; i < l; i++ ) {
7946 					elem = checkSet[i];
7947 
7948 					if ( elem ) {
7949 						checkSet[i] = isPartStr ?
7950 							elem.parentNode :
7951 							elem.parentNode === part;
7952 					}
7953 				}
7954 
7955 				if ( isPartStr ) {
7956 					Sizzle.filter( part, checkSet, true );
7957 				}
7958 			}
7959 		},
7960 
7961 		"": function(checkSet, part, isXML){
7962 			var nodeCheck,
7963 				doneName = done++,
7964 				checkFn = dirCheck;
7965 
7966 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7967 				part = part.toLowerCase();
7968 				nodeCheck = part;
7969 				checkFn = dirNodeCheck;
7970 			}
7971 
7972 			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
7973 		},
7974 
7975 		"~": function( checkSet, part, isXML ) {
7976 			var nodeCheck,
7977 				doneName = done++,
7978 				checkFn = dirCheck;
7979 
7980 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7981 				part = part.toLowerCase();
7982 				nodeCheck = part;
7983 				checkFn = dirNodeCheck;
7984 			}
7985 
7986 			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
7987 		}
7988 	},
7989 
7990 	find: {
7991 		ID: function( match, context, isXML ) {
7992 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
7993 				var m = context.getElementById(match[1]);
7994 				// Check parentNode to catch when Blackberry 4.6 returns
7995 				// nodes that are no longer in the document #6963
7996 				return m && m.parentNode ? [m] : [];
7997 			}
7998 		},
7999 
8000 		NAME: function( match, context ) {
8001 			if ( typeof context.getElementsByName !== "undefined" ) {
8002 				var ret = [],
8003 					results = context.getElementsByName( match[1] );
8004 
8005 				for ( var i = 0, l = results.length; i < l; i++ ) {
8006 					if ( results[i].getAttribute("name") === match[1] ) {
8007 						ret.push( results[i] );
8008 					}
8009 				}
8010 
8011 				return ret.length === 0 ? null : ret;
8012 			}
8013 		},
8014 
8015 		TAG: function( match, context ) {
8016 			if ( typeof context.getElementsByTagName !== "undefined" ) {
8017 				return context.getElementsByTagName( match[1] );
8018 			}
8019 		}
8020 	},
8021 	preFilter: {
8022 		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
8023 			match = " " + match[1].replace( rBackslash, "" ) + " ";
8024 
8025 			if ( isXML ) {
8026 				return match;
8027 			}
8028 
8029 			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
8030 				if ( elem ) {
8031 					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
8032 						if ( !inplace ) {
8033 							result.push( elem );
8034 						}
8035 
8036 					} else if ( inplace ) {
8037 						curLoop[i] = false;
8038 					}
8039 				}
8040 			}
8041 
8042 			return false;
8043 		},
8044 
8045 		ID: function( match ) {
8046 			return match[1].replace( rBackslash, "" );
8047 		},
8048 
8049 		TAG: function( match, curLoop ) {
8050 			return match[1].replace( rBackslash, "" ).toLowerCase();
8051 		},
8052 
8053 		CHILD: function( match ) {
8054 			if ( match[1] === "nth" ) {
8055 				if ( !match[2] ) {
8056 					Sizzle.error( match[0] );
8057 				}
8058 
8059 				match[2] = match[2].replace(/^\+|\s*/g, '');
8060 
8061 				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
8062 				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
8063 					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
8064 					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
8065 
8066 				// calculate the numbers (first)n+(last) including if they are negative
8067 				match[2] = (test[1] + (test[2] || 1)) - 0;
8068 				match[3] = test[3] - 0;
8069 			}
8070 			else if ( match[2] ) {
8071 				Sizzle.error( match[0] );
8072 			}
8073 
8074 			// TODO: Move to normal caching system
8075 			match[0] = done++;
8076 
8077 			return match;
8078 		},
8079 
8080 		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
8081 			var name = match[1] = match[1].replace( rBackslash, "" );
8082 
8083 			if ( !isXML && Expr.attrMap[name] ) {
8084 				match[1] = Expr.attrMap[name];
8085 			}
8086 
8087 			// Handle if an un-quoted value was used
8088 			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
8089 
8090 			if ( match[2] === "~=" ) {
8091 				match[4] = " " + match[4] + " ";
8092 			}
8093 
8094 			return match;
8095 		},
8096 
8097 		PSEUDO: function( match, curLoop, inplace, result, not ) {
8098 			if ( match[1] === "not" ) {
8099 				// If we're dealing with a complex expression, or a simple one
8100 				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
8101 					match[3] = Sizzle(match[3], null, null, curLoop);
8102 
8103 				} else {
8104 					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
8105 
8106 					if ( !inplace ) {
8107 						result.push.apply( result, ret );
8108 					}
8109 
8110 					return false;
8111 				}
8112 
8113 			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
8114 				return true;
8115 			}
8116 
8117 			return match;
8118 		},
8119 
8120 		POS: function( match ) {
8121 			match.unshift( true );
8122 
8123 			return match;
8124 		}
8125 	},
8126 
8127 	filters: {
8128 		enabled: function( elem ) {
8129 			return elem.disabled === false && elem.type !== "hidden";
8130 		},
8131 
8132 		disabled: function( elem ) {
8133 			return elem.disabled === true;
8134 		},
8135 
8136 		checked: function( elem ) {
8137 			return elem.checked === true;
8138 		},
8139 
8140 		selected: function( elem ) {
8141 			// Accessing this property makes selected-by-default
8142 			// options in Safari work properly
8143 			if ( elem.parentNode ) {
8144 				elem.parentNode.selectedIndex;
8145 			}
8146 
8147 			return elem.selected === true;
8148 		},
8149 
8150 		parent: function( elem ) {
8151 			return !!elem.firstChild;
8152 		},
8153 
8154 		empty: function( elem ) {
8155 			return !elem.firstChild;
8156 		},
8157 
8158 		has: function( elem, i, match ) {
8159 			return !!Sizzle( match[3], elem ).length;
8160 		},
8161 
8162 		header: function( elem ) {
8163 			return (/h\d/i).test( elem.nodeName );
8164 		},
8165 
8166 		text: function( elem ) {
8167 			var attr = elem.getAttribute( "type" ), type = elem.type;
8168 			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
8169 			// use getAttribute instead to test this case
8170 			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
8171 		},
8172 
8173 		radio: function( elem ) {
8174 			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
8175 		},
8176 
8177 		checkbox: function( elem ) {
8178 			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
8179 		},
8180 
8181 		file: function( elem ) {
8182 			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
8183 		},
8184 
8185 		password: function( elem ) {
8186 			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
8187 		},
8188 
8189 		submit: function( elem ) {
8190 			var name = elem.nodeName.toLowerCase();
8191 			return (name === "input" || name === "button") && "submit" === elem.type;
8192 		},
8193 
8194 		image: function( elem ) {
8195 			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
8196 		},
8197 
8198 		reset: function( elem ) {
8199 			var name = elem.nodeName.toLowerCase();
8200 			return (name === "input" || name === "button") && "reset" === elem.type;
8201 		},
8202 
8203 		button: function( elem ) {
8204 			var name = elem.nodeName.toLowerCase();
8205 			return name === "input" && "button" === elem.type || name === "button";
8206 		},
8207 
8208 		input: function( elem ) {
8209 			return (/input|select|textarea|button/i).test( elem.nodeName );
8210 		},
8211 
8212 		focus: function( elem ) {
8213 			return elem === elem.ownerDocument.activeElement;
8214 		}
8215 	},
8216 	setFilters: {
8217 		first: function( elem, i ) {
8218 			return i === 0;
8219 		},
8220 
8221 		last: function( elem, i, match, array ) {
8222 			return i === array.length - 1;
8223 		},
8224 
8225 		even: function( elem, i ) {
8226 			return i % 2 === 0;
8227 		},
8228 
8229 		odd: function( elem, i ) {
8230 			return i % 2 === 1;
8231 		},
8232 
8233 		lt: function( elem, i, match ) {
8234 			return i < match[3] - 0;
8235 		},
8236 
8237 		gt: function( elem, i, match ) {
8238 			return i > match[3] - 0;
8239 		},
8240 
8241 		nth: function( elem, i, match ) {
8242 			return match[3] - 0 === i;
8243 		},
8244 
8245 		eq: function( elem, i, match ) {
8246 			return match[3] - 0 === i;
8247 		}
8248 	},
8249 	filter: {
8250 		PSEUDO: function( elem, match, i, array ) {
8251 			var name = match[1],
8252 				filter = Expr.filters[ name ];
8253 
8254 			if ( filter ) {
8255 				return filter( elem, i, match, array );
8256 
8257 			} else if ( name === "contains" ) {
8258 				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
8259 
8260 			} else if ( name === "not" ) {
8261 				var not = match[3];
8262 
8263 				for ( var j = 0, l = not.length; j < l; j++ ) {
8264 					if ( not[j] === elem ) {
8265 						return false;
8266 					}
8267 				}
8268 
8269 				return true;
8270 
8271 			} else {
8272 				Sizzle.error( name );
8273 			}
8274 		},
8275 
8276 		CHILD: function( elem, match ) {
8277 			var first, last,
8278 				doneName, parent, cache,
8279 				count, diff,
8280 				type = match[1],
8281 				node = elem;
8282 
8283 			switch ( type ) {
8284 				case "only":
8285 				case "first":
8286 					while ( (node = node.previousSibling) ) {
8287 						if ( node.nodeType === 1 ) {
8288 							return false;
8289 						}
8290 					}
8291 
8292 					if ( type === "first" ) {
8293 						return true;
8294 					}
8295 
8296 					node = elem;
8297 
8298 					/* falls through */
8299 				case "last":
8300 					while ( (node = node.nextSibling) ) {
8301 						if ( node.nodeType === 1 ) {
8302 							return false;
8303 						}
8304 					}
8305 
8306 					return true;
8307 
8308 				case "nth":
8309 					first = match[2];
8310 					last = match[3];
8311 
8312 					if ( first === 1 && last === 0 ) {
8313 						return true;
8314 					}
8315 
8316 					doneName = match[0];
8317 					parent = elem.parentNode;
8318 
8319 					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
8320 						count = 0;
8321 
8322 						for ( node = parent.firstChild; node; node = node.nextSibling ) {
8323 							if ( node.nodeType === 1 ) {
8324 								node.nodeIndex = ++count;
8325 							}
8326 						}
8327 
8328 						parent[ expando ] = doneName;
8329 					}
8330 
8331 					diff = elem.nodeIndex - last;
8332 
8333 					if ( first === 0 ) {
8334 						return diff === 0;
8335 
8336 					} else {
8337 						return ( diff % first === 0 && diff / first >= 0 );
8338 					}
8339 			}
8340 		},
8341 
8342 		ID: function( elem, match ) {
8343 			return elem.nodeType === 1 && elem.getAttribute("id") === match;
8344 		},
8345 
8346 		TAG: function( elem, match ) {
8347 			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
8348 		},
8349 
8350 		CLASS: function( elem, match ) {
8351 			return (" " + (elem.className || elem.getAttribute("class")) + " ")
8352 				.indexOf( match ) > -1;
8353 		},
8354 
8355 		ATTR: function( elem, match ) {
8356 			var name = match[1],
8357 				result = Sizzle.attr ?
8358 					Sizzle.attr( elem, name ) :
8359 					Expr.attrHandle[ name ] ?
8360 					Expr.attrHandle[ name ]( elem ) :
8361 					elem[ name ] != null ?
8362 						elem[ name ] :
8363 						elem.getAttribute( name ),
8364 				value = result + "",
8365 				type = match[2],
8366 				check = match[4];
8367 
8368 			return result == null ?
8369 				type === "!=" :
8370 				!type && Sizzle.attr ?
8371 				result != null :
8372 				type === "=" ?
8373 				value === check :
8374 				type === "*=" ?
8375 				value.indexOf(check) >= 0 :
8376 				type === "~=" ?
8377 				(" " + value + " ").indexOf(check) >= 0 :
8378 				!check ?
8379 				value && result !== false :
8380 				type === "!=" ?
8381 				value !== check :
8382 				type === "^=" ?
8383 				value.indexOf(check) === 0 :
8384 				type === "$=" ?
8385 				value.substr(value.length - check.length) === check :
8386 				type === "|=" ?
8387 				value === check || value.substr(0, check.length + 1) === check + "-" :
8388 				false;
8389 		},
8390 
8391 		POS: function( elem, match, i, array ) {
8392 			var name = match[2],
8393 				filter = Expr.setFilters[ name ];
8394 
8395 			if ( filter ) {
8396 				return filter( elem, i, match, array );
8397 			}
8398 		}
8399 	}
8400 };
8401 
8402 var origPOS = Expr.match.POS,
8403 	fescape = function(all, num){
8404 		return "\\" + (num - 0 + 1);
8405 	};
8406 
8407 for ( var type in Expr.match ) {
8408 	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
8409 	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
8410 }
8411 // Expose origPOS
8412 // "global" as in regardless of relation to brackets/parens
8413 Expr.match.globalPOS = origPOS;
8414 
8415 var makeArray = function( array, results ) {
8416 	array = Array.prototype.slice.call( array, 0 );
8417 
8418 	if ( results ) {
8419 		results.push.apply( results, array );
8420 		return results;
8421 	}
8422 
8423 	return array;
8424 };
8425 
8426 // Perform a simple check to determine if the browser is capable of
8427 // converting a NodeList to an array using builtin methods.
8428 // Also verifies that the returned array holds DOM nodes
8429 // (which is not the case in the Blackberry browser)
8430 try {
8431 	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
8432 
8433 // Provide a fallback method if it does not work
8434 } catch( e ) {
8435 	makeArray = function( array, results ) {
8436 		var i = 0,
8437 			ret = results || [];
8438 
8439 		if ( toString.call(array) === "[object Array]" ) {
8440 			Array.prototype.push.apply( ret, array );
8441 
8442 		} else {
8443 			if ( typeof array.length === "number" ) {
8444 				for ( var l = array.length; i < l; i++ ) {
8445 					ret.push( array[i] );
8446 				}
8447 
8448 			} else {
8449 				for ( ; array[i]; i++ ) {
8450 					ret.push( array[i] );
8451 				}
8452 			}
8453 		}
8454 
8455 		return ret;
8456 	};
8457 }
8458 
8459 var sortOrder, siblingCheck;
8460 
8461 if ( document.documentElement.compareDocumentPosition ) {
8462 	sortOrder = function( a, b ) {
8463 		if ( a === b ) {
8464 			hasDuplicate = true;
8465 			return 0;
8466 		}
8467 
8468 		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
8469 			return a.compareDocumentPosition ? -1 : 1;
8470 		}
8471 
8472 		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
8473 	};
8474 
8475 } else {
8476 	sortOrder = function( a, b ) {
8477 		// The nodes are identical, we can exit early
8478 		if ( a === b ) {
8479 			hasDuplicate = true;
8480 			return 0;
8481 
8482 		// Fallback to using sourceIndex (in IE) if it's available on both nodes
8483 		} else if ( a.sourceIndex && b.sourceIndex ) {
8484 			return a.sourceIndex - b.sourceIndex;
8485 		}
8486 
8487 		var al, bl,
8488 			ap = [],
8489 			bp = [],
8490 			aup = a.parentNode,
8491 			bup = b.parentNode,
8492 			cur = aup;
8493 
8494 		// If the nodes are siblings (or identical) we can do a quick check
8495 		if ( aup === bup ) {
8496 			return siblingCheck( a, b );
8497 
8498 		// If no parents were found then the nodes are disconnected
8499 		} else if ( !aup ) {
8500 			return -1;
8501 
8502 		} else if ( !bup ) {
8503 			return 1;
8504 		}
8505 
8506 		// Otherwise they're somewhere else in the tree so we need
8507 		// to build up a full list of the parentNodes for comparison
8508 		while ( cur ) {
8509 			ap.unshift( cur );
8510 			cur = cur.parentNode;
8511 		}
8512 
8513 		cur = bup;
8514 
8515 		while ( cur ) {
8516 			bp.unshift( cur );
8517 			cur = cur.parentNode;
8518 		}
8519 
8520 		al = ap.length;
8521 		bl = bp.length;
8522 
8523 		// Start walking down the tree looking for a discrepancy
8524 		for ( var i = 0; i < al && i < bl; i++ ) {
8525 			if ( ap[i] !== bp[i] ) {
8526 				return siblingCheck( ap[i], bp[i] );
8527 			}
8528 		}
8529 
8530 		// We ended someplace up the tree so do a sibling check
8531 		return i === al ?
8532 			siblingCheck( a, bp[i], -1 ) :
8533 			siblingCheck( ap[i], b, 1 );
8534 	};
8535 
8536 	siblingCheck = function( a, b, ret ) {
8537 		if ( a === b ) {
8538 			return ret;
8539 		}
8540 
8541 		var cur = a.nextSibling;
8542 
8543 		while ( cur ) {
8544 			if ( cur === b ) {
8545 				return -1;
8546 			}
8547 
8548 			cur = cur.nextSibling;
8549 		}
8550 
8551 		return 1;
8552 	};
8553 }
8554 
8555 // Check to see if the browser returns elements by name when
8556 // querying by getElementById (and provide a workaround)
8557 (function(){
8558 	// We're going to inject a fake input element with a specified name
8559 	var form = document.createElement("div"),
8560 		id = "script" + (new Date()).getTime(),
8561 		root = document.documentElement;
8562 
8563 	form.innerHTML = "<a name='" + id + "'/>";
8564 
8565 	// Inject it into the root element, check its status, and remove it quickly
8566 	root.insertBefore( form, root.firstChild );
8567 
8568 	// The workaround has to do additional checks after a getElementById
8569 	// Which slows things down for other browsers (hence the branching)
8570 	if ( document.getElementById( id ) ) {
8571 		Expr.find.ID = function( match, context, isXML ) {
8572 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
8573 				var m = context.getElementById(match[1]);
8574 
8575 				return m ?
8576 					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
8577 						[m] :
8578 						undefined :
8579 					[];
8580 			}
8581 		};
8582 
8583 		Expr.filter.ID = function( elem, match ) {
8584 			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
8585 
8586 			return elem.nodeType === 1 && node && node.nodeValue === match;
8587 		};
8588 	}
8589 
8590 	root.removeChild( form );
8591 
8592 	// release memory in IE
8593 	root = form = null;
8594 })();
8595 
8596 (function(){
8597 	// Check to see if the browser returns only elements
8598 	// when doing getElementsByTagName("*")
8599 
8600 	// Create a fake element
8601 	var div = document.createElement("div");
8602 	div.appendChild( document.createComment("") );
8603 
8604 	// Make sure no comments are found
8605 	if ( div.getElementsByTagName("*").length > 0 ) {
8606 		Expr.find.TAG = function( match, context ) {
8607 			var results = context.getElementsByTagName( match[1] );
8608 
8609 			// Filter out possible comments
8610 			if ( match[1] === "*" ) {
8611 				var tmp = [];
8612 
8613 				for ( var i = 0; results[i]; i++ ) {
8614 					if ( results[i].nodeType === 1 ) {
8615 						tmp.push( results[i] );
8616 					}
8617 				}
8618 
8619 				results = tmp;
8620 			}
8621 
8622 			return results;
8623 		};
8624 	}
8625 
8626 	// Check to see if an attribute returns normalized href attributes
8627 	div.innerHTML = "<a href='#'></a>";
8628 
8629 	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
8630 			div.firstChild.getAttribute("href") !== "#" ) {
8631 
8632 		Expr.attrHandle.href = function( elem ) {
8633 			return elem.getAttribute( "href", 2 );
8634 		};
8635 	}
8636 
8637 	// release memory in IE
8638 	div = null;
8639 })();
8640 
8641 if ( document.querySelectorAll ) {
8642 	(function(){
8643 		var oldSizzle = Sizzle,
8644 			div = document.createElement("div"),
8645 			id = "__sizzle__";
8646 
8647 		div.innerHTML = "<p class='TEST'></p>";
8648 
8649 		// Safari can't handle uppercase or unicode characters when
8650 		// in quirks mode.
8651 		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
8652 			return;
8653 		}
8654 
8655 		Sizzle = function( query, context, extra, seed ) {
8656 			context = context || document;
8657 
8658 			// Only use querySelectorAll on non-XML documents
8659 			// (ID selectors don't work in non-HTML documents)
8660 			if ( !seed && !Sizzle.isXML(context) ) {
8661 				// See if we find a selector to speed up
8662 				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
8663 
8664 				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
8665 					// Speed-up: Sizzle("TAG")
8666 					if ( match[1] ) {
8667 						return makeArray( context.getElementsByTagName( query ), extra );
8668 
8669 					// Speed-up: Sizzle(".CLASS")
8670 					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
8671 						return makeArray( context.getElementsByClassName( match[2] ), extra );
8672 					}
8673 				}
8674 
8675 				if ( context.nodeType === 9 ) {
8676 					// Speed-up: Sizzle("body")
8677 					// The body element only exists once, optimize finding it
8678 					if ( query === "body" && context.body ) {
8679 						return makeArray( [ context.body ], extra );
8680 
8681 					// Speed-up: Sizzle("#ID")
8682 					} else if ( match && match[3] ) {
8683 						var elem = context.getElementById( match[3] );
8684 
8685 						// Check parentNode to catch when Blackberry 4.6 returns
8686 						// nodes that are no longer in the document #6963
8687 						if ( elem && elem.parentNode ) {
8688 							// Handle the case where IE and Opera return items
8689 							// by name instead of ID
8690 							if ( elem.id === match[3] ) {
8691 								return makeArray( [ elem ], extra );
8692 							}
8693 
8694 						} else {
8695 							return makeArray( [], extra );
8696 						}
8697 					}
8698 
8699 					try {
8700 						return makeArray( context.querySelectorAll(query), extra );
8701 					} catch(qsaError) {}
8702 
8703 				// qSA works strangely on Element-rooted queries
8704 				// We can work around this by specifying an extra ID on the root
8705 				// and working up from there (Thanks to Andrew Dupont for the technique)
8706 				// IE 8 doesn't work on object elements
8707 				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
8708 					var oldContext = context,
8709 						old = context.getAttribute( "id" ),
8710 						nid = old || id,
8711 						hasParent = context.parentNode,
8712 						relativeHierarchySelector = /^\s*[+~]/.test( query );
8713 
8714 					if ( !old ) {
8715 						context.setAttribute( "id", nid );
8716 					} else {
8717 						nid = nid.replace( /'/g, "\\$&" );
8718 					}
8719 					if ( relativeHierarchySelector && hasParent ) {
8720 						context = context.parentNode;
8721 					}
8722 
8723 					try {
8724 						if ( !relativeHierarchySelector || hasParent ) {
8725 							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
8726 						}
8727 
8728 					} catch(pseudoError) {
8729 					} finally {
8730 						if ( !old ) {
8731 							oldContext.removeAttribute( "id" );
8732 						}
8733 					}
8734 				}
8735 			}
8736 
8737 			return oldSizzle(query, context, extra, seed);
8738 		};
8739 
8740 		for ( var prop in oldSizzle ) {
8741 			Sizzle[ prop ] = oldSizzle[ prop ];
8742 		}
8743 
8744 		// release memory in IE
8745 		div = null;
8746 	})();
8747 }
8748 
8749 (function(){
8750 	var html = document.documentElement,
8751 		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
8752 
8753 	if ( matches ) {
8754 		// Check to see if it's possible to do matchesSelector
8755 		// on a disconnected node (IE 9 fails this)
8756 		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
8757 			pseudoWorks = false;
8758 
8759 		try {
8760 			// This should fail with an exception
8761 			// Gecko does not error, returns false instead
8762 			matches.call( document.documentElement, "[test!='']:sizzle" );
8763 
8764 		} catch( pseudoError ) {
8765 			pseudoWorks = true;
8766 		}
8767 
8768 		Sizzle.matchesSelector = function( node, expr ) {
8769 			// Make sure that attribute selectors are quoted
8770 			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
8771 
8772 			if ( !Sizzle.isXML( node ) ) {
8773 				try {
8774 					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
8775 						var ret = matches.call( node, expr );
8776 
8777 						// IE 9's matchesSelector returns false on disconnected nodes
8778 						if ( ret || !disconnectedMatch ||
8779 								// As well, disconnected nodes are said to be in a document
8780 								// fragment in IE 9, so check for that
8781 								node.document && node.document.nodeType !== 11 ) {
8782 							return ret;
8783 						}
8784 					}
8785 				} catch(e) {}
8786 			}
8787 
8788 			return Sizzle(expr, null, null, [node]).length > 0;
8789 		};
8790 	}
8791 })();
8792 
8793 (function(){
8794 	var div = document.createElement("div");
8795 
8796 	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
8797 
8798 	// Opera can't find a second classname (in 9.6)
8799 	// Also, make sure that getElementsByClassName actually exists
8800 	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
8801 		return;
8802 	}
8803 
8804 	// Safari caches class attributes, doesn't catch changes (in 3.2)
8805 	div.lastChild.className = "e";
8806 
8807 	if ( div.getElementsByClassName("e").length === 1 ) {
8808 		return;
8809 	}
8810 
8811 	Expr.order.splice(1, 0, "CLASS");
8812 	Expr.find.CLASS = function( match, context, isXML ) {
8813 		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
8814 			return context.getElementsByClassName(match[1]);
8815 		}
8816 	};
8817 
8818 	// release memory in IE
8819 	div = null;
8820 })();
8821 
8822 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8823 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8824 		var elem = checkSet[i];
8825 
8826 		if ( elem ) {
8827 			var match = false;
8828 
8829 			elem = elem[dir];
8830 
8831 			while ( elem ) {
8832 				if ( elem[ expando ] === doneName ) {
8833 					match = checkSet[elem.sizset];
8834 					break;
8835 				}
8836 
8837 				if ( elem.nodeType === 1 && !isXML ){
8838 					elem[ expando ] = doneName;
8839 					elem.sizset = i;
8840 				}
8841 
8842 				if ( elem.nodeName.toLowerCase() === cur ) {
8843 					match = elem;
8844 					break;
8845 				}
8846 
8847 				elem = elem[dir];
8848 			}
8849 
8850 			checkSet[i] = match;
8851 		}
8852 	}
8853 }
8854 
8855 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8856 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8857 		var elem = checkSet[i];
8858 
8859 		if ( elem ) {
8860 			var match = false;
8861 
8862 			elem = elem[dir];
8863 
8864 			while ( elem ) {
8865 				if ( elem[ expando ] === doneName ) {
8866 					match = checkSet[elem.sizset];
8867 					break;
8868 				}
8869 
8870 				if ( elem.nodeType === 1 ) {
8871 					if ( !isXML ) {
8872 						elem[ expando ] = doneName;
8873 						elem.sizset = i;
8874 					}
8875 
8876 					if ( typeof cur !== "string" ) {
8877 						if ( elem === cur ) {
8878 							match = true;
8879 							break;
8880 						}
8881 
8882 					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
8883 						match = elem;
8884 						break;
8885 					}
8886 				}
8887 
8888 				elem = elem[dir];
8889 			}
8890 
8891 			checkSet[i] = match;
8892 		}
8893 	}
8894 }
8895 
8896 if ( document.documentElement.contains ) {
8897 	Sizzle.contains = function( a, b ) {
8898 		return a !== b && (a.contains ? a.contains(b) : true);
8899 	};
8900 
8901 } else if ( document.documentElement.compareDocumentPosition ) {
8902 	Sizzle.contains = function( a, b ) {
8903 		return !!(a.compareDocumentPosition(b) & 16);
8904 	};
8905 
8906 } else {
8907 	Sizzle.contains = function() {
8908 		return false;
8909 	};
8910 }
8911 
8912 Sizzle.isXML = function( elem ) {
8913 	// documentElement is verified for cases where it doesn't yet exist
8914 	// (such as loading iframes in IE - #4833)
8915 	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
8916 
8917 	return documentElement ? documentElement.nodeName !== "HTML" : false;
8918 };
8919 
8920 var posProcess = function( selector, context, seed ) {
8921 	var match,
8922 		tmpSet = [],
8923 		later = "",
8924 		root = context.nodeType ? [context] : context;
8925 
8926 	// Position selectors must be done after the filter
8927 	// And so must :not(positional) so we move all PSEUDOs to the end
8928 	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
8929 		later += match[0];
8930 		selector = selector.replace( Expr.match.PSEUDO, "" );
8931 	}
8932 
8933 	selector = Expr.relative[selector] ? selector + "*" : selector;
8934 
8935 	for ( var i = 0, l = root.length; i < l; i++ ) {
8936 		Sizzle( selector, root[i], tmpSet, seed );
8937 	}
8938 
8939 	return Sizzle.filter( later, tmpSet );
8940 };
8941 
8942 // EXPOSE
8943 
8944 window.tinymce.dom.Sizzle = Sizzle;
8945 
8946 })();
8947 
8948 
8949 (function(tinymce) {
8950 	tinymce.dom.Element = function(id, settings) {
8951 		var t = this, dom, el;
8952 
8953 		t.settings = settings = settings || {};
8954 		t.id = id;
8955 		t.dom = dom = settings.dom || tinymce.DOM;
8956 
8957 		// Only IE leaks DOM references, this is a lot faster
8958 		if (!tinymce.isIE)
8959 			el = dom.get(t.id);
8960 
8961 		tinymce.each(
8962 				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
8963 				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
8964 				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
8965 				'isHidden,setHTML,get').split(/,/), function(k) {
8966 					t[k] = function() {
8967 						var a = [id], i;
8968 
8969 						for (i = 0; i < arguments.length; i++)
8970 							a.push(arguments[i]);
8971 
8972 						a = dom[k].apply(dom, a);
8973 						t.update(k);
8974 
8975 						return a;
8976 					};
8977 			}
8978 		);
8979 
8980 		tinymce.extend(t, {
8981 			on : function(n, f, s) {
8982 				return tinymce.dom.Event.add(t.id, n, f, s);
8983 			},
8984 
8985 			getXY : function() {
8986 				return {
8987 					x : parseInt(t.getStyle('left')),
8988 					y : parseInt(t.getStyle('top'))
8989 				};
8990 			},
8991 
8992 			getSize : function() {
8993 				var n = dom.get(t.id);
8994 
8995 				return {
8996 					w : parseInt(t.getStyle('width') || n.clientWidth),
8997 					h : parseInt(t.getStyle('height') || n.clientHeight)
8998 				};
8999 			},
9000 
9001 			moveTo : function(x, y) {
9002 				t.setStyles({left : x, top : y});
9003 			},
9004 
9005 			moveBy : function(x, y) {
9006 				var p = t.getXY();
9007 
9008 				t.moveTo(p.x + x, p.y + y);
9009 			},
9010 
9011 			resizeTo : function(w, h) {
9012 				t.setStyles({width : w, height : h});
9013 			},
9014 
9015 			resizeBy : function(w, h) {
9016 				var s = t.getSize();
9017 
9018 				t.resizeTo(s.w + w, s.h + h);
9019 			},
9020 
9021 			update : function(k) {
9022 				var b;
9023 
9024 				if (tinymce.isIE6 && settings.blocker) {
9025 					k = k || '';
9026 
9027 					// Ignore getters
9028 					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
9029 						return;
9030 
9031 					// Remove blocker on remove
9032 					if (k == 'remove') {
9033 						dom.remove(t.blocker);
9034 						return;
9035 					}
9036 
9037 					if (!t.blocker) {
9038 						t.blocker = dom.uniqueId();
9039 						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
9040 						dom.setStyle(b, 'opacity', 0);
9041 					} else
9042 						b = dom.get(t.blocker);
9043 
9044 					dom.setStyles(b, {
9045 						left : t.getStyle('left', 1),
9046 						top : t.getStyle('top', 1),
9047 						width : t.getStyle('width', 1),
9048 						height : t.getStyle('height', 1),
9049 						display : t.getStyle('display', 1),
9050 						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
9051 					});
9052 				}
9053 			}
9054 		});
9055 	};
9056 })(tinymce);
9057 
9058 (function(tinymce) {
9059 	function trimNl(s) {
9060 		return s.replace(/[\n\r]+/g, '');
9061 	};
9062 
9063 	// Shorten names
9064 	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
9065 
9066 	tinymce.create('tinymce.dom.Selection', {
9067 		Selection : function(dom, win, serializer, editor) {
9068 			var t = this;
9069 
9070 			t.dom = dom;
9071 			t.win = win;
9072 			t.serializer = serializer;
9073 			t.editor = editor;
9074 
9075 			// Add events
9076 			each([
9077 				'onBeforeSetContent',
9078 
9079 				'onBeforeGetContent',
9080 
9081 				'onSetContent',
9082 
9083 				'onGetContent'
9084 			], function(e) {
9085 				t[e] = new tinymce.util.Dispatcher(t);
9086 			});
9087 
9088 			// No W3C Range support
9089 			if (!t.win.getSelection)
9090 				t.tridentSel = new tinymce.dom.TridentSelection(t);
9091 
9092 			if (tinymce.isIE && dom.boxModel)
9093 				this._fixIESelection();
9094 
9095 			// Prevent leaks
9096 			tinymce.addUnload(t.destroy, t);
9097 		},
9098 
9099 		setCursorLocation: function(node, offset) {
9100 			var t = this; var r = t.dom.createRng();
9101 			r.setStart(node, offset);
9102 			r.setEnd(node, offset);
9103 			t.setRng(r);
9104 			t.collapse(false);
9105 		},
9106 		getContent : function(s) {
9107 			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
9108 
9109 			s = s || {};
9110 			wb = wa = '';
9111 			s.get = true;
9112 			s.format = s.format || 'html';
9113 			s.forced_root_block = '';
9114 			t.onBeforeGetContent.dispatch(t, s);
9115 
9116 			if (s.format == 'text')
9117 				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
9118 
9119 			if (r.cloneContents) {
9120 				n = r.cloneContents();
9121 
9122 				if (n)
9123 					e.appendChild(n);
9124 			} else if (is(r.item) || is(r.htmlText)) {
9125 				// IE will produce invalid markup if elements are present that
9126 				// it doesn't understand like custom elements or HTML5 elements.
9127 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
9128 				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
9129 				e.removeChild(e.firstChild);
9130 			} else
9131 				e.innerHTML = r.toString();
9132 
9133 			// Keep whitespace before and after
9134 			if (/^\s/.test(e.innerHTML))
9135 				wb = ' ';
9136 
9137 			if (/\s+$/.test(e.innerHTML))
9138 				wa = ' ';
9139 
9140 			s.getInner = true;
9141 
9142 			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
9143 			t.onGetContent.dispatch(t, s);
9144 
9145 			return s.content;
9146 		},
9147 
9148 		setContent : function(content, args) {
9149 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9150 
9151 			args = args || {format : 'html'};
9152 			args.set = true;
9153 			content = args.content = content;
9154 
9155 			// Dispatch before set content event
9156 			if (!args.no_events)
9157 				self.onBeforeSetContent.dispatch(self, args);
9158 
9159 			content = args.content;
9160 
9161 			if (rng.insertNode) {
9162 				// Make caret marker since insertNode places the caret in the beginning of text after insert
9163 				content += '<span id="__caret">_</span>';
9164 
9165 				// Delete and insert new node
9166 				if (rng.startContainer == doc && rng.endContainer == doc) {
9167 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9168 					doc.body.innerHTML = content;
9169 				} else {
9170 					rng.deleteContents();
9171 
9172 					if (doc.body.childNodes.length === 0) {
9173 						doc.body.innerHTML = content;
9174 					} else {
9175 						// createContextualFragment doesn't exists in IE 9 DOMRanges
9176 						if (rng.createContextualFragment) {
9177 							rng.insertNode(rng.createContextualFragment(content));
9178 						} else {
9179 							// Fake createContextualFragment call in IE 9
9180 							frag = doc.createDocumentFragment();
9181 							temp = doc.createElement('div');
9182 
9183 							frag.appendChild(temp);
9184 							temp.outerHTML = content;
9185 
9186 							rng.insertNode(frag);
9187 						}
9188 					}
9189 				}
9190 
9191 				// Move to caret marker
9192 				caretNode = self.dom.get('__caret');
9193 
9194 				// Make sure we wrap it compleatly, Opera fails with a simple select call
9195 				rng = doc.createRange();
9196 				rng.setStartBefore(caretNode);
9197 				rng.setEndBefore(caretNode);
9198 				self.setRng(rng);
9199 
9200 				// Remove the caret position
9201 				self.dom.remove('__caret');
9202 
9203 				try {
9204 					self.setRng(rng);
9205 				} catch (ex) {
9206 					// Might fail on Opera for some odd reason
9207 				}
9208 			} else {
9209 				if (rng.item) {
9210 					// Delete content and get caret text selection
9211 					doc.execCommand('Delete', false, null);
9212 					rng = self.getRng();
9213 				}
9214 
9215 				// Explorer removes spaces from the beginning of pasted contents
9216 				if (/^\s+/.test(content)) {
9217 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9218 					self.dom.remove('__mce_tmp');
9219 				} else
9220 					rng.pasteHTML(content);
9221 			}
9222 
9223 			// Dispatch set content event
9224 			if (!args.no_events)
9225 				self.onSetContent.dispatch(self, args);
9226 		},
9227 
9228 		getStart : function() {
9229 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9230 
9231 			if (rng.duplicate || rng.item) {
9232 				// Control selection, return first item
9233 				if (rng.item)
9234 					return rng.item(0);
9235 
9236 				// Get start element
9237 				checkRng = rng.duplicate();
9238 				checkRng.collapse(1);
9239 				startElement = checkRng.parentElement();
9240 				if (startElement.ownerDocument !== self.dom.doc) {
9241 					startElement = self.dom.getRoot();
9242 				}
9243 
9244 				// Check if range parent is inside the start element, then return the inner parent element
9245 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9246 				parentElement = node = rng.parentElement();
9247 				while (node = node.parentNode) {
9248 					if (node == startElement) {
9249 						startElement = parentElement;
9250 						break;
9251 					}
9252 				}
9253 
9254 				return startElement;
9255 			} else {
9256 				startElement = rng.startContainer;
9257 
9258 				if (startElement.nodeType == 1 && startElement.hasChildNodes())
9259 					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9260 
9261 				if (startElement && startElement.nodeType == 3)
9262 					return startElement.parentNode;
9263 
9264 				return startElement;
9265 			}
9266 		},
9267 
9268 		getEnd : function() {
9269 			var self = this, rng = self.getRng(), endElement, endOffset;
9270 
9271 			if (rng.duplicate || rng.item) {
9272 				if (rng.item)
9273 					return rng.item(0);
9274 
9275 				rng = rng.duplicate();
9276 				rng.collapse(0);
9277 				endElement = rng.parentElement();
9278 				if (endElement.ownerDocument !== self.dom.doc) {
9279 					endElement = self.dom.getRoot();
9280 				}
9281 
9282 				if (endElement && endElement.nodeName == 'BODY')
9283 					return endElement.lastChild || endElement;
9284 
9285 				return endElement;
9286 			} else {
9287 				endElement = rng.endContainer;
9288 				endOffset = rng.endOffset;
9289 
9290 				if (endElement.nodeType == 1 && endElement.hasChildNodes())
9291 					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9292 
9293 				if (endElement && endElement.nodeType == 3)
9294 					return endElement.parentNode;
9295 
9296 				return endElement;
9297 			}
9298 		},
9299 
9300 		getBookmark : function(type, normalized) {
9301 			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
9302 
9303 			function findIndex(name, element) {
9304 				var index = 0;
9305 
9306 				each(dom.select(name), function(node, i) {
9307 					if (node == element)
9308 						index = i;
9309 				});
9310 
9311 				return index;
9312 			};
9313 
9314 			function normalizeTableCellSelection(rng) {
9315 				function moveEndPoint(start) {
9316 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
9317 
9318 					container = rng[prefix + 'Container'];
9319 					offset = rng[prefix + 'Offset'];
9320 
9321 					if (container.nodeType == 1 && container.nodeName == "TR") {
9322 						childNodes = container.childNodes;
9323 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9324 						if (container) {
9325 							offset = start ? 0 : container.childNodes.length;
9326 							rng['set' + (start ? 'Start' : 'End')](container, offset);
9327 						}
9328 					}
9329 				};
9330 
9331 				moveEndPoint(true);
9332 				moveEndPoint();
9333 
9334 				return rng;
9335 			};
9336 
9337 			function getLocation() {
9338 				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9339 
9340 				function getPoint(rng, start) {
9341 					var container = rng[start ? 'startContainer' : 'endContainer'],
9342 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9343 
9344 					if (container.nodeType == 3) {
9345 						if (normalized) {
9346 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
9347 								offset += node.nodeValue.length;
9348 						}
9349 
9350 						point.push(offset);
9351 					} else {
9352 						childNodes = container.childNodes;
9353 
9354 						if (offset >= childNodes.length && childNodes.length) {
9355 							after = 1;
9356 							offset = Math.max(0, childNodes.length - 1);
9357 						}
9358 
9359 						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
9360 					}
9361 
9362 					for (; container && container != root; container = container.parentNode)
9363 						point.push(t.dom.nodeIndex(container, normalized));
9364 
9365 					return point;
9366 				};
9367 
9368 				bookmark.start = getPoint(rng, true);
9369 
9370 				if (!t.isCollapsed())
9371 					bookmark.end = getPoint(rng);
9372 
9373 				return bookmark;
9374 			};
9375 
9376 			if (type == 2) {
9377 				if (t.tridentSel)
9378 					return t.tridentSel.getBookmark(type);
9379 
9380 				return getLocation();
9381 			}
9382 
9383 			// Handle simple range
9384 			if (type)
9385 				return {rng : t.getRng()};
9386 
9387 			rng = t.getRng();
9388 			id = dom.uniqueId();
9389 			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
9390 			styles = 'overflow:hidden;line-height:0px';
9391 
9392 			// Explorer method
9393 			if (rng.duplicate || rng.item) {
9394 				// Text selection
9395 				if (!rng.item) {
9396 					rng2 = rng.duplicate();
9397 
9398 					try {
9399 						// Insert start marker
9400 						rng.collapse();
9401 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
9402 
9403 						// Insert end marker
9404 						if (!collapsed) {
9405 							rng2.collapse(false);
9406 
9407 							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
9408 							rng.moveToElementText(rng2.parentElement());
9409 							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
9410 								rng2.move('character', -1);
9411 
9412 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
9413 						}
9414 					} catch (ex) {
9415 						// IE might throw unspecified error so lets ignore it
9416 						return null;
9417 					}
9418 				} else {
9419 					// Control selection
9420 					element = rng.item(0);
9421 					name = element.nodeName;
9422 
9423 					return {name : name, index : findIndex(name, element)};
9424 				}
9425 			} else {
9426 				element = t.getNode();
9427 				name = element.nodeName;
9428 				if (name == 'IMG')
9429 					return {name : name, index : findIndex(name, element)};
9430 
9431 				// W3C method
9432 				rng2 = normalizeTableCellSelection(rng.cloneRange());
9433 
9434 				// Insert end marker
9435 				if (!collapsed) {
9436 					rng2.collapse(false);
9437 					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9438 				}
9439 
9440 				rng = normalizeTableCellSelection(rng);
9441 				rng.collapse(true);
9442 				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9443 			}
9444 
9445 			t.moveToBookmark({id : id, keep : 1});
9446 
9447 			return {id : id};
9448 		},
9449 
9450 		moveToBookmark : function(bookmark) {
9451 			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
9452 
9453 			function setEndPoint(start) {
9454 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
9455 
9456 				if (point) {
9457 					offset = point[0];
9458 
9459 					// Find container node
9460 					for (node = root, i = point.length - 1; i >= 1; i--) {
9461 						children = node.childNodes;
9462 
9463 						if (point[i] > children.length - 1)
9464 							return;
9465 
9466 						node = children[point[i]];
9467 					}
9468 
9469 					// Move text offset to best suitable location
9470 					if (node.nodeType === 3)
9471 						offset = Math.min(point[0], node.nodeValue.length);
9472 
9473 					// Move element offset to best suitable location
9474 					if (node.nodeType === 1)
9475 						offset = Math.min(point[0], node.childNodes.length);
9476 
9477 					// Set offset within container node
9478 					if (start)
9479 						rng.setStart(node, offset);
9480 					else
9481 						rng.setEnd(node, offset);
9482 				}
9483 
9484 				return true;
9485 			};
9486 
9487 			function restoreEndPoint(suffix) {
9488 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
9489 
9490 				if (marker) {
9491 					node = marker.parentNode;
9492 
9493 					if (suffix == 'start') {
9494 						if (!keep) {
9495 							idx = dom.nodeIndex(marker);
9496 						} else {
9497 							node = marker.firstChild;
9498 							idx = 1;
9499 						}
9500 
9501 						startContainer = endContainer = node;
9502 						startOffset = endOffset = idx;
9503 					} else {
9504 						if (!keep) {
9505 							idx = dom.nodeIndex(marker);
9506 						} else {
9507 							node = marker.firstChild;
9508 							idx = 1;
9509 						}
9510 
9511 						endContainer = node;
9512 						endOffset = idx;
9513 					}
9514 
9515 					if (!keep) {
9516 						prev = marker.previousSibling;
9517 						next = marker.nextSibling;
9518 
9519 						// Remove all marker text nodes
9520 						each(tinymce.grep(marker.childNodes), function(node) {
9521 							if (node.nodeType == 3)
9522 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
9523 						});
9524 
9525 						// Remove marker but keep children if for example contents where inserted into the marker
9526 						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
9527 						while (marker = dom.get(bookmark.id + '_' + suffix))
9528 							dom.remove(marker, 1);
9529 
9530 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
9531 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
9532 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
9533 							idx = prev.nodeValue.length;
9534 							prev.appendData(next.nodeValue);
9535 							dom.remove(next);
9536 
9537 							if (suffix == 'start') {
9538 								startContainer = endContainer = prev;
9539 								startOffset = endOffset = idx;
9540 							} else {
9541 								endContainer = prev;
9542 								endOffset = idx;
9543 							}
9544 						}
9545 					}
9546 				}
9547 			};
9548 
9549 			function addBogus(node) {
9550 				// Adds a bogus BR element for empty block elements
9551 				if (dom.isBlock(node) && !node.innerHTML && !isIE)
9552 					node.innerHTML = '<br data-mce-bogus="1" />';
9553 
9554 				return node;
9555 			};
9556 
9557 			if (bookmark) {
9558 				if (bookmark.start) {
9559 					rng = dom.createRng();
9560 					root = dom.getRoot();
9561 
9562 					if (t.tridentSel)
9563 						return t.tridentSel.moveToBookmark(bookmark);
9564 
9565 					if (setEndPoint(true) && setEndPoint()) {
9566 						t.setRng(rng);
9567 					}
9568 				} else if (bookmark.id) {
9569 					// Restore start/end points
9570 					restoreEndPoint('start');
9571 					restoreEndPoint('end');
9572 
9573 					if (startContainer) {
9574 						rng = dom.createRng();
9575 						rng.setStart(addBogus(startContainer), startOffset);
9576 						rng.setEnd(addBogus(endContainer), endOffset);
9577 						t.setRng(rng);
9578 					}
9579 				} else if (bookmark.name) {
9580 					t.select(dom.select(bookmark.name)[bookmark.index]);
9581 				} else if (bookmark.rng)
9582 					t.setRng(bookmark.rng);
9583 			}
9584 		},
9585 
9586 		select : function(node, content) {
9587 			var t = this, dom = t.dom, rng = dom.createRng(), idx;
9588 
9589 			function setPoint(node, start) {
9590 				var walker = new TreeWalker(node, node);
9591 
9592 				do {
9593 					// Text node
9594 					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
9595 						if (start)
9596 							rng.setStart(node, 0);
9597 						else
9598 							rng.setEnd(node, node.nodeValue.length);
9599 
9600 						return;
9601 					}
9602 
9603 					// BR element
9604 					if (node.nodeName == 'BR') {
9605 						if (start)
9606 							rng.setStartBefore(node);
9607 						else
9608 							rng.setEndBefore(node);
9609 
9610 						return;
9611 					}
9612 				} while (node = (start ? walker.next() : walker.prev()));
9613 			};
9614 
9615 			if (node) {
9616 				idx = dom.nodeIndex(node);
9617 				rng.setStart(node.parentNode, idx);
9618 				rng.setEnd(node.parentNode, idx + 1);
9619 
9620 				// Find first/last text node or BR element
9621 				if (content) {
9622 					setPoint(node, 1);
9623 					setPoint(node);
9624 				}
9625 
9626 				t.setRng(rng);
9627 			}
9628 
9629 			return node;
9630 		},
9631 
9632 		isCollapsed : function() {
9633 			var t = this, r = t.getRng(), s = t.getSel();
9634 
9635 			if (!r || r.item)
9636 				return false;
9637 
9638 			if (r.compareEndPoints)
9639 				return r.compareEndPoints('StartToEnd', r) === 0;
9640 
9641 			return !s || r.collapsed;
9642 		},
9643 
9644 		collapse : function(to_start) {
9645 			var self = this, rng = self.getRng(), node;
9646 
9647 			// Control range on IE
9648 			if (rng.item) {
9649 				node = rng.item(0);
9650 				rng = self.win.document.body.createTextRange();
9651 				rng.moveToElementText(node);
9652 			}
9653 
9654 			rng.collapse(!!to_start);
9655 			self.setRng(rng);
9656 		},
9657 
9658 		getSel : function() {
9659 			var t = this, w = this.win;
9660 
9661 			return w.getSelection ? w.getSelection() : w.document.selection;
9662 		},
9663 
9664 		getRng : function(w3c) {
9665 			var self = this, selection, rng, elm, doc = self.win.document;
9666 
9667 			// Found tridentSel object then we need to use that one
9668 			if (w3c && self.tridentSel) {
9669 				return self.tridentSel.getRangeAt(0);
9670 			}
9671 
9672 			try {
9673 				if (selection = self.getSel()) {
9674 					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9675 				}
9676 			} catch (ex) {
9677 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9678 			}
9679 
9680 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
9681 			if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
9682 				elm = doc.selection.createRange().item(0);
9683 				rng = doc.createRange();
9684 				rng.setStartBefore(elm);
9685 				rng.setEndAfter(elm);
9686 			}
9687 
9688 			// No range found then create an empty one
9689 			// This can occur when the editor is placed in a hidden container element on Gecko
9690 			// Or on IE when there was an exception
9691 			if (!rng) {
9692 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
9693 			}
9694 
9695 			// If range is at start of document then move it to start of body
9696 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
9697 				elm = self.dom.getRoot();
9698 				rng.setStart(elm, 0);
9699 				rng.setEnd(elm, 0);
9700 			}
9701 
9702 			if (self.selectedRange && self.explicitRange) {
9703 				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
9704 					// Safari, Opera and Chrome only ever select text which causes the range to change.
9705 					// This lets us use the originally set range if the selection hasn't been changed by the user.
9706 					rng = self.explicitRange;
9707 				} else {
9708 					self.selectedRange = null;
9709 					self.explicitRange = null;
9710 				}
9711 			}
9712 
9713 			return rng;
9714 		},
9715 
9716 		setRng : function(r, forward) {
9717 			var s, t = this;
9718 
9719 			if (!t.tridentSel) {
9720 				s = t.getSel();
9721 
9722 				if (s) {
9723 					t.explicitRange = r;
9724 
9725 					try {
9726 						s.removeAllRanges();
9727 					} catch (ex) {
9728 						// IE9 might throw errors here don't know why
9729 					}
9730 
9731 					s.addRange(r);
9732 
9733 					// Forward is set to false and we have an extend function
9734 					if (forward === false && s.extend) {
9735 						s.collapse(r.endContainer, r.endOffset);
9736 						s.extend(r.startContainer, r.startOffset);
9737 					}
9738 
9739 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
9740 					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
9741 				}
9742 			} else {
9743 				// Is W3C Range
9744 				if (r.cloneRange) {
9745 					try {
9746 						t.tridentSel.addRange(r);
9747 						return;
9748 					} catch (ex) {
9749 						//IE9 throws an error here if called before selection is placed in the editor
9750 					}
9751 				}
9752 
9753 				// Is IE specific range
9754 				try {
9755 					r.select();
9756 				} catch (ex) {
9757 					// Needed for some odd IE bug #1843306
9758 				}
9759 			}
9760 		},
9761 
9762 		setNode : function(n) {
9763 			var t = this;
9764 
9765 			t.setContent(t.dom.getOuterHTML(n));
9766 
9767 			return n;
9768 		},
9769 
9770 		getNode : function() {
9771 			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
9772 
9773 			function skipEmptyTextNodes(n, forwards) {
9774 				var orig = n;
9775 				while (n && n.nodeType === 3 && n.length === 0) {
9776 					n = forwards ? n.nextSibling : n.previousSibling;
9777 				}
9778 				return n || orig;
9779 			};
9780 
9781 			// Range maybe lost after the editor is made visible again
9782 			if (!rng)
9783 				return t.dom.getRoot();
9784 
9785 			if (rng.setStart) {
9786 				elm = rng.commonAncestorContainer;
9787 
9788 				// Handle selection a image or other control like element such as anchors
9789 				if (!rng.collapsed) {
9790 					if (rng.startContainer == rng.endContainer) {
9791 						if (rng.endOffset - rng.startOffset < 2) {
9792 							if (rng.startContainer.hasChildNodes())
9793 								elm = rng.startContainer.childNodes[rng.startOffset];
9794 						}
9795 					}
9796 
9797 					// If the anchor node is a element instead of a text node then return this element
9798 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
9799 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
9800 
9801 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
9802 					// This happens when you double click an underlined word in FireFox.
9803 					if (start.nodeType === 3 && end.nodeType === 3) {
9804 						if (start.length === rng.startOffset) {
9805 							start = skipEmptyTextNodes(start.nextSibling, true);
9806 						} else {
9807 							start = start.parentNode;
9808 						}
9809 						if (rng.endOffset === 0) {
9810 							end = skipEmptyTextNodes(end.previousSibling, false);
9811 						} else {
9812 							end = end.parentNode;
9813 						}
9814 
9815 						if (start && start === end)
9816 							return start;
9817 					}
9818 				}
9819 
9820 				if (elm && elm.nodeType == 3)
9821 					return elm.parentNode;
9822 
9823 				return elm;
9824 			}
9825 
9826 			return rng.item ? rng.item(0) : rng.parentElement();
9827 		},
9828 
9829 		getSelectedBlocks : function(st, en) {
9830 			var t = this, dom = t.dom, sb, eb, n, bl = [];
9831 
9832 			sb = dom.getParent(st || t.getStart(), dom.isBlock);
9833 			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
9834 
9835 			if (sb)
9836 				bl.push(sb);
9837 
9838 			if (sb && eb && sb != eb) {
9839 				n = sb;
9840 
9841 				var walker = new TreeWalker(sb, dom.getRoot());
9842 				while ((n = walker.next()) && n != eb) {
9843 					if (dom.isBlock(n))
9844 						bl.push(n);
9845 				}
9846 			}
9847 
9848 			if (eb && sb != eb)
9849 				bl.push(eb);
9850 
9851 			return bl;
9852 		},
9853 
9854 		isForward: function(){
9855 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
9856 
9857 			// No support for selection direction then always return true
9858 			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
9859 				return true;
9860 			}
9861 
9862 			anchorRange = dom.createRng();
9863 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
9864 			anchorRange.collapse(true);
9865 
9866 			focusRange = dom.createRng();
9867 			focusRange.setStart(sel.focusNode, sel.focusOffset);
9868 			focusRange.collapse(true);
9869 
9870 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
9871 		},
9872 
9873 		normalize : function() {
9874 			var self = this, rng, normalized, collapsed, node, sibling;
9875 
9876 			function normalizeEndPoint(start) {
9877 				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
9878 
9879 				function hasBrBeforeAfter(node, left) {
9880 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
9881 
9882 					while (node = walker[left ? 'prev' : 'next']()) {
9883 						if (node.nodeName === "BR") {
9884 							return true;
9885 						}
9886 					}
9887 				};
9888 
9889 				// Walks the dom left/right to find a suitable text node to move the endpoint into
9890 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
9891 				function findTextNodeRelative(left, startNode) {
9892 					var walker, lastInlineElement;
9893 
9894 					startNode = startNode || container;
9895 					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
9896 
9897 					// Walk left until we hit a text node we can move to or a block/br/img
9898 					while (node = walker[left ? 'prev' : 'next']()) {
9899 						// Found text node that has a length
9900 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
9901 							container = node;
9902 							offset = left ? node.nodeValue.length : 0;
9903 							normalized = true;
9904 							return;
9905 						}
9906 
9907 						// Break if we find a block or a BR/IMG/INPUT etc
9908 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9909 							return;
9910 						}
9911 
9912 						lastInlineElement = node;
9913 					}
9914 
9915 					// Only fetch the last inline element when in caret mode for now
9916 					if (collapsed && lastInlineElement) {
9917 						container = lastInlineElement;
9918 						normalized = true;
9919 						offset = 0;
9920 					}
9921 				};
9922 
9923 				container = rng[(start ? 'start' : 'end') + 'Container'];
9924 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
9925 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
9926 
9927 				// If the container is a document move it to the body element
9928 				if (container.nodeType === 9) {
9929 					container = dom.getRoot();
9930 					offset = 0;
9931 				}
9932 
9933 				// If the container is body try move it into the closest text node or position
9934 				if (container === body) {
9935 					// If start is before/after a image, table etc
9936 					if (start) {
9937 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
9938 						if (node) {
9939 							nodeName = node.nodeName.toLowerCase();
9940 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
9941 								return;
9942 							}
9943 						}
9944 					}
9945 
9946 					// Resolve the index
9947 					if (container.hasChildNodes()) {
9948 						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
9949 						offset = 0;
9950 
9951 						// Don't walk into elements that doesn't have any child nodes like a IMG
9952 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
9953 							// Walk the DOM to find a text node to place the caret at or a BR
9954 							node = container;
9955 							walker = new TreeWalker(container, body);
9956 
9957 							do {
9958 								// Found a text node use that position
9959 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
9960 									offset = start ? 0 : node.nodeValue.length;
9961 									container = node;
9962 									normalized = true;
9963 									break;
9964 								}
9965 
9966 								// Found a BR/IMG element that we can place the caret before
9967 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9968 									offset = dom.nodeIndex(node);
9969 									container = node.parentNode;
9970 
9971 									// Put caret after image when moving the end point
9972 									if (node.nodeName ==  "IMG" && !start) {
9973 										offset++;
9974 									}
9975 
9976 									normalized = true;
9977 									break;
9978 								}
9979 							} while (node = (start ? walker.next() : walker.prev()));
9980 						}
9981 					}
9982 				}
9983 
9984 				// Lean the caret to the left if possible
9985 				if (collapsed) {
9986 					// So this: <b>x</b><i>|x</i>
9987 					// Becomes: <b>x|</b><i>x</i>
9988 					// Seems that only gecko has issues with this
9989 					if (container.nodeType === 3 && offset === 0) {
9990 						findTextNodeRelative(true);
9991 					}
9992 
9993 					// Lean left into empty inline elements when the caret is before a BR
9994 					// So this: <i><b></b><i>|<br></i>
9995 					// Becomes: <i><b>|</b><i><br></i>
9996 					// Seems that only gecko has issues with this
9997 					if (container.nodeType === 1) {
9998 						node = container.childNodes[offset];
9999 						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
10000 							findTextNodeRelative(true, container.childNodes[offset]);
10001 						}
10002 					}
10003 				}
10004 
10005 				// Lean the start of the selection right if possible
10006 				// So this: x[<b>x]</b>
10007 				// Becomes: x<b>[x]</b>
10008 				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
10009 					findTextNodeRelative(false);
10010 				}
10011 
10012 				// Set endpoint if it was normalized
10013 				if (normalized)
10014 					rng['set' + (start ? 'Start' : 'End')](container, offset);
10015 			};
10016 
10017 			// Normalize only on non IE browsers for now
10018 			if (tinymce.isIE)
10019 				return;
10020 			
10021 			rng = self.getRng();
10022 			collapsed = rng.collapsed;
10023 
10024 			// Normalize the end points
10025 			normalizeEndPoint(true);
10026 
10027 			if (!collapsed)
10028 				normalizeEndPoint();
10029 
10030 			// Set the selection if it was normalized
10031 			if (normalized) {
10032 				// If it was collapsed then make sure it still is
10033 				if (collapsed) {
10034 					rng.collapse(true);
10035 				}
10036 
10037 				//console.log(self.dom.dumpRng(rng));
10038 				self.setRng(rng, self.isForward());
10039 			}
10040 		},
10041 
10042 		selectorChanged: function(selector, callback) {
10043 			var self = this, currentSelectors;
10044 
10045 			if (!self.selectorChangedData) {
10046 				self.selectorChangedData = {};
10047 				currentSelectors = {};
10048 
10049 				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
10050 					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10051 
10052 					// Check for new matching selectors
10053 					each(self.selectorChangedData, function(callbacks, selector) {
10054 						each(parents, function(node) {
10055 							if (dom.is(node, selector)) {
10056 								if (!currentSelectors[selector]) {
10057 									// Execute callbacks
10058 									each(callbacks, function(callback) {
10059 										callback(true, {node: node, selector: selector, parents: parents});
10060 									});
10061 
10062 									currentSelectors[selector] = callbacks;
10063 								}
10064 
10065 								matchedSelectors[selector] = callbacks;
10066 								return false;
10067 							}
10068 						});
10069 					});
10070 
10071 					// Check if current selectors still match
10072 					each(currentSelectors, function(callbacks, selector) {
10073 						if (!matchedSelectors[selector]) {
10074 							delete currentSelectors[selector];
10075 
10076 							each(callbacks, function(callback) {
10077 								callback(false, {node: node, selector: selector, parents: parents});
10078 							});
10079 						}
10080 					});
10081 				});
10082 			}
10083 
10084 			// Add selector listeners
10085 			if (!self.selectorChangedData[selector]) {
10086 				self.selectorChangedData[selector] = [];
10087 			}
10088 
10089 			self.selectorChangedData[selector].push(callback);
10090 
10091 			return self;
10092 		},
10093 
10094 		destroy : function(manual) {
10095 			var self = this;
10096 
10097 			self.win = null;
10098 
10099 			// Manual destroy then remove unload handler
10100 			if (!manual)
10101 				tinymce.removeUnload(self.destroy);
10102 		},
10103 
10104 		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
10105 		_fixIESelection : function() {
10106 			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
10107 
10108 			// Return range from point or null if it failed
10109 			function rngFromPoint(x, y) {
10110 				var rng = body.createTextRange();
10111 
10112 				try {
10113 					rng.moveToPoint(x, y);
10114 				} catch (ex) {
10115 					// IE sometimes throws and exception, so lets just ignore it
10116 					rng = null;
10117 				}
10118 
10119 				return rng;
10120 			};
10121 
10122 			// Fires while the selection is changing
10123 			function selectionChange(e) {
10124 				var pointRng;
10125 
10126 				// Check if the button is down or not
10127 				if (e.button) {
10128 					// Create range from mouse position
10129 					pointRng = rngFromPoint(e.x, e.y);
10130 
10131 					if (pointRng) {
10132 						// Check if pointRange is before/after selection then change the endPoint
10133 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
10134 							pointRng.setEndPoint('StartToStart', startRng);
10135 						else
10136 							pointRng.setEndPoint('EndToEnd', startRng);
10137 
10138 						pointRng.select();
10139 					}
10140 				} else
10141 					endSelection();
10142 			}
10143 
10144 			// Removes listeners
10145 			function endSelection() {
10146 				var rng = doc.selection.createRange();
10147 
10148 				// If the range is collapsed then use the last start range
10149 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
10150 					startRng.select();
10151 
10152 				dom.unbind(doc, 'mouseup', endSelection);
10153 				dom.unbind(doc, 'mousemove', selectionChange);
10154 				startRng = started = 0;
10155 			};
10156 
10157 			// Make HTML element unselectable since we are going to handle selection by hand
10158 			doc.documentElement.unselectable = true;
10159 			
10160 			// Detect when user selects outside BODY
10161 			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
10162 				if (e.target.nodeName === 'HTML') {
10163 					if (started)
10164 						endSelection();
10165 
10166 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
10167 					htmlElm = doc.documentElement;
10168 					if (htmlElm.scrollHeight > htmlElm.clientHeight)
10169 						return;
10170 
10171 					started = 1;
10172 					// Setup start position
10173 					startRng = rngFromPoint(e.x, e.y);
10174 					if (startRng) {
10175 						// Listen for selection change events
10176 						dom.bind(doc, 'mouseup', endSelection);
10177 						dom.bind(doc, 'mousemove', selectionChange);
10178 
10179 						dom.win.focus();
10180 						startRng.select();
10181 					}
10182 				}
10183 			});
10184 		}
10185 	});
10186 })(tinymce);
10187 
10188 (function(tinymce) {
10189 	tinymce.dom.Serializer = function(settings, dom, schema) {
10190 		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
10191 
10192 		// Support the old apply_source_formatting option
10193 		if (!settings.apply_source_formatting)
10194 			settings.indent = false;
10195 
10196 		// Default DOM and Schema if they are undefined
10197 		dom = dom || tinymce.DOM;
10198 		schema = schema || new tinymce.html.Schema(settings);
10199 		settings.entity_encoding = settings.entity_encoding || 'named';
10200 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10201 
10202 		onPreProcess = new tinymce.util.Dispatcher(self);
10203 
10204 		onPostProcess = new tinymce.util.Dispatcher(self);
10205 
10206 		htmlParser = new tinymce.html.DomParser(settings, schema);
10207 
10208 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10209 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10210 			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10211 
10212 			while (i--) {
10213 				node = nodes[i];
10214 
10215 				value = node.attributes.map[internalName];
10216 				if (value !== undef) {
10217 					// Set external name to internal value and remove internal
10218 					node.attr(name, value.length > 0 ? value : null);
10219 					node.attr(internalName, null);
10220 				} else {
10221 					// No internal attribute found then convert the value we have in the DOM
10222 					value = node.attributes.map[name];
10223 
10224 					if (name === "style")
10225 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
10226 					else if (urlConverter)
10227 						value = urlConverter.call(urlConverterScope, value, name, node.name);
10228 
10229 					node.attr(name, value.length > 0 ? value : null);
10230 				}
10231 			}
10232 		});
10233 
10234 		// Remove internal classes mceItem<..> or mceSelected
10235 		htmlParser.addAttributeFilter('class', function(nodes, name) {
10236 			var i = nodes.length, node, value;
10237 
10238 			while (i--) {
10239 				node = nodes[i];
10240 				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
10241 				node.attr('class', value.length > 0 ? value : null);
10242 			}
10243 		});
10244 
10245 		// Remove bookmark elements
10246 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10247 			var i = nodes.length, node;
10248 
10249 			while (i--) {
10250 				node = nodes[i];
10251 
10252 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
10253 					node.remove();
10254 			}
10255 		});
10256 
10257 		// Remove expando attributes
10258 		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
10259 			var i = nodes.length;
10260 
10261 			while (i--) {
10262 				nodes[i].attr(name, null);
10263 			}
10264 		});
10265 
10266 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
10267 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
10268 			var i = nodes.length, node, value;
10269 
10270 			function trim(value) {
10271 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10272 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
10273 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10274 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10275 			};
10276 
10277 			while (i--) {
10278 				node = nodes[i];
10279 				value = node.firstChild ? node.firstChild.value : '';
10280 
10281 				if (name === "script") {
10282 					// Remove mce- prefix from script elements
10283 					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
10284 
10285 					if (value.length > 0)
10286 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10287 				} else {
10288 					if (value.length > 0)
10289 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10290 				}
10291 			}
10292 		});
10293 
10294 		// Convert comments to cdata and handle protected comments
10295 		htmlParser.addNodeFilter('#comment', function(nodes, name) {
10296 			var i = nodes.length, node;
10297 
10298 			while (i--) {
10299 				node = nodes[i];
10300 
10301 				if (node.value.indexOf('[CDATA[') === 0) {
10302 					node.name = '#cdata';
10303 					node.type = 4;
10304 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10305 				} else if (node.value.indexOf('mce:protected ') === 0) {
10306 					node.name = "#text";
10307 					node.type = 3;
10308 					node.raw = true;
10309 					node.value = unescape(node.value).substr(14);
10310 				}
10311 			}
10312 		});
10313 
10314 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10315 			var i = nodes.length, node;
10316 
10317 			while (i--) {
10318 				node = nodes[i];
10319 				if (node.type === 7)
10320 					node.remove();
10321 				else if (node.type === 1) {
10322 					if (name === "input" && !("type" in node.attributes.map))
10323 						node.attr('type', 'text');
10324 				}
10325 			}
10326 		});
10327 
10328 		// Fix list elements, TODO: Replace this later
10329 		if (settings.fix_list_elements) {
10330 			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
10331 				var i = nodes.length, node, parentNode;
10332 
10333 				while (i--) {
10334 					node = nodes[i];
10335 					parentNode = node.parent;
10336 
10337 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10338 						if (node.prev && node.prev.name === 'li') {
10339 							node.prev.append(node);
10340 						}
10341 					}
10342 				}
10343 			});
10344 		}
10345 
10346 		// Remove internal data attributes
10347 		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
10348 			var i = nodes.length;
10349 
10350 			while (i--) {
10351 				nodes[i].attr(name, null);
10352 			}
10353 		});
10354 
10355 		// Return public methods
10356 		return {
10357 			schema : schema,
10358 
10359 			addNodeFilter : htmlParser.addNodeFilter,
10360 
10361 			addAttributeFilter : htmlParser.addAttributeFilter,
10362 
10363 			onPreProcess : onPreProcess,
10364 
10365 			onPostProcess : onPostProcess,
10366 
10367 			serialize : function(node, args) {
10368 				var impl, doc, oldDoc, htmlSerializer, content;
10369 
10370 				// Explorer won't clone contents of script and style and the
10371 				// selected index of select elements are cleared on a clone operation.
10372 				if (isIE && dom.select('script,style,select,map').length > 0) {
10373 					content = node.innerHTML;
10374 					node = node.cloneNode(false);
10375 					dom.setHTML(node, content);
10376 				} else
10377 					node = node.cloneNode(true);
10378 
10379 				// Nodes needs to be attached to something in WebKit/Opera
10380 				// Older builds of Opera crashes if you attach the node to an document created dynamically
10381 				// and since we can't feature detect a crash we need to sniff the acutal build number
10382 				// This fix will make DOM ranges and make Sizzle happy!
10383 				impl = node.ownerDocument.implementation;
10384 				if (impl.createHTMLDocument) {
10385 					// Create an empty HTML document
10386 					doc = impl.createHTMLDocument("");
10387 
10388 					// Add the element or it's children if it's a body element to the new document
10389 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
10390 						doc.body.appendChild(doc.importNode(node, true));
10391 					});
10392 
10393 					// Grab first child or body element for serialization
10394 					if (node.nodeName != 'BODY')
10395 						node = doc.body.firstChild;
10396 					else
10397 						node = doc.body;
10398 
10399 					// set the new document in DOMUtils so createElement etc works
10400 					oldDoc = dom.doc;
10401 					dom.doc = doc;
10402 				}
10403 
10404 				args = args || {};
10405 				args.format = args.format || 'html';
10406 
10407 				// Pre process
10408 				if (!args.no_events) {
10409 					args.node = node;
10410 					onPreProcess.dispatch(self, args);
10411 				}
10412 
10413 				// Setup serializer
10414 				htmlSerializer = new tinymce.html.Serializer(settings, schema);
10415 
10416 				// Parse and serialize HTML
10417 				args.content = htmlSerializer.serialize(
10418 					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
10419 				);
10420 
10421 				// Replace all BOM characters for now until we can find a better solution
10422 				if (!args.cleanup)
10423 					args.content = args.content.replace(/\uFEFF|\u200B/g, '');
10424 
10425 				// Post process
10426 				if (!args.no_events)
10427 					onPostProcess.dispatch(self, args);
10428 
10429 				// Restore the old document if it was changed
10430 				if (oldDoc)
10431 					dom.doc = oldDoc;
10432 
10433 				args.node = null;
10434 
10435 				return args.content;
10436 			},
10437 
10438 			addRules : function(rules) {
10439 				schema.addValidElements(rules);
10440 			},
10441 
10442 			setRules : function(rules) {
10443 				schema.setValidElements(rules);
10444 			}
10445 		};
10446 	};
10447 })(tinymce);
10448 (function(tinymce) {
10449 	tinymce.dom.ScriptLoader = function(settings) {
10450 		var QUEUED = 0,
10451 			LOADING = 1,
10452 			LOADED = 2,
10453 			states = {},
10454 			queue = [],
10455 			scriptLoadedCallbacks = {},
10456 			queueLoadedCallbacks = [],
10457 			loading = 0,
10458 			undef;
10459 
10460 		function loadScript(url, callback) {
10461 			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
10462 
10463 			// Execute callback when script is loaded
10464 			function done() {
10465 				dom.remove(id);
10466 
10467 				if (elm)
10468 					elm.onreadystatechange = elm.onload = elm = null;
10469 
10470 				callback();
10471 			};
10472 			
10473 			function error() {
10474 				// Report the error so it's easier for people to spot loading errors
10475 				if (typeof(console) !== "undefined" && console.log)
10476 					console.log("Failed to load: " + url);
10477 
10478 				// We can't mark it as done if there is a load error since
10479 				// A) We don't want to produce 404 errors on the server and
10480 				// B) the onerror event won't fire on all browsers.
10481 				// done();
10482 			};
10483 
10484 			id = dom.uniqueId();
10485 
10486 			if (tinymce.isIE6) {
10487 				uri = new tinymce.util.URI(url);
10488 				loc = location;
10489 
10490 				// If script is from same domain and we
10491 				// use IE 6 then use XHR since it's more reliable
10492 				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
10493 					tinymce.util.XHR.send({
10494 						url : tinymce._addVer(uri.getURI()),
10495 						success : function(content) {
10496 							// Create new temp script element
10497 							var script = dom.create('script', {
10498 								type : 'text/javascript'
10499 							});
10500 
10501 							// Evaluate script in global scope
10502 							script.text = content;
10503 							document.getElementsByTagName('head')[0].appendChild(script);
10504 							dom.remove(script);
10505 
10506 							done();
10507 						},
10508 						
10509 						error : error
10510 					});
10511 
10512 					return;
10513 				}
10514 			}
10515 
10516 			// Create new script element
10517 			elm = document.createElement('script');
10518 			elm.id = id;
10519 			elm.type = 'text/javascript';
10520 			elm.src = tinymce._addVer(url);
10521 
10522 			// Add onload listener for non IE browsers since IE9
10523 			// fires onload event before the script is parsed and executed
10524 			if (!tinymce.isIE)
10525 				elm.onload = done;
10526 
10527 			// Add onerror event will get fired on some browsers but not all of them
10528 			elm.onerror = error;
10529 
10530 			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
10531 			if (!tinymce.isOpera) {
10532 				elm.onreadystatechange = function() {
10533 					var state = elm.readyState;
10534 
10535 					// Loaded state is passed on IE 6 however there
10536 					// are known issues with this method but we can't use
10537 					// XHR in a cross domain loading
10538 					if (state == 'complete' || state == 'loaded')
10539 						done();
10540 				};
10541 			}
10542 
10543 			// Most browsers support this feature so we report errors
10544 			// for those at least to help users track their missing plugins etc
10545 			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
10546 			/*elm.onerror = function() {
10547 				alert('Failed to load: ' + url);
10548 			};*/
10549 
10550 			// Add script to document
10551 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
10552 		};
10553 
10554 		this.isDone = function(url) {
10555 			return states[url] == LOADED;
10556 		};
10557 
10558 		this.markDone = function(url) {
10559 			states[url] = LOADED;
10560 		};
10561 
10562 		this.add = this.load = function(url, callback, scope) {
10563 			var item, state = states[url];
10564 
10565 			// Add url to load queue
10566 			if (state == undef) {
10567 				queue.push(url);
10568 				states[url] = QUEUED;
10569 			}
10570 
10571 			if (callback) {
10572 				// Store away callback for later execution
10573 				if (!scriptLoadedCallbacks[url])
10574 					scriptLoadedCallbacks[url] = [];
10575 
10576 				scriptLoadedCallbacks[url].push({
10577 					func : callback,
10578 					scope : scope || this
10579 				});
10580 			}
10581 		};
10582 
10583 		this.loadQueue = function(callback, scope) {
10584 			this.loadScripts(queue, callback, scope);
10585 		};
10586 
10587 		this.loadScripts = function(scripts, callback, scope) {
10588 			var loadScripts;
10589 
10590 			function execScriptLoadedCallbacks(url) {
10591 				// Execute URL callback functions
10592 				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
10593 					callback.func.call(callback.scope);
10594 				});
10595 
10596 				scriptLoadedCallbacks[url] = undef;
10597 			};
10598 
10599 			queueLoadedCallbacks.push({
10600 				func : callback,
10601 				scope : scope || this
10602 			});
10603 
10604 			loadScripts = function() {
10605 				var loadingScripts = tinymce.grep(scripts);
10606 
10607 				// Current scripts has been handled
10608 				scripts.length = 0;
10609 
10610 				// Load scripts that needs to be loaded
10611 				tinymce.each(loadingScripts, function(url) {
10612 					// Script is already loaded then execute script callbacks directly
10613 					if (states[url] == LOADED) {
10614 						execScriptLoadedCallbacks(url);
10615 						return;
10616 					}
10617 
10618 					// Is script not loading then start loading it
10619 					if (states[url] != LOADING) {
10620 						states[url] = LOADING;
10621 						loading++;
10622 
10623 						loadScript(url, function() {
10624 							states[url] = LOADED;
10625 							loading--;
10626 
10627 							execScriptLoadedCallbacks(url);
10628 
10629 							// Load more scripts if they where added by the recently loaded script
10630 							loadScripts();
10631 						});
10632 					}
10633 				});
10634 
10635 				// No scripts are currently loading then execute all pending queue loaded callbacks
10636 				if (!loading) {
10637 					tinymce.each(queueLoadedCallbacks, function(callback) {
10638 						callback.func.call(callback.scope);
10639 					});
10640 
10641 					queueLoadedCallbacks.length = 0;
10642 				}
10643 			};
10644 
10645 			loadScripts();
10646 		};
10647 	};
10648 
10649 	// Global script loader
10650 	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
10651 })(tinymce);
10652 
10653 (function(tinymce) {
10654 	tinymce.dom.RangeUtils = function(dom) {
10655 		var INVISIBLE_CHAR = '\uFEFF';
10656 
10657 		this.walk = function(rng, callback) {
10658 			var startContainer = rng.startContainer,
10659 				startOffset = rng.startOffset,
10660 				endContainer = rng.endContainer,
10661 				endOffset = rng.endOffset,
10662 				ancestor, startPoint,
10663 				endPoint, node, parent, siblings, nodes;
10664 
10665 			// Handle table cell selection the table plugin enables
10666 			// you to fake select table cells and perform formatting actions on them
10667 			nodes = dom.select('td.mceSelected,th.mceSelected');
10668 			if (nodes.length > 0) {
10669 				tinymce.each(nodes, function(node) {
10670 					callback([node]);
10671 				});
10672 
10673 				return;
10674 			}
10675 
10676 			function exclude(nodes) {
10677 				var node;
10678 
10679 				// First node is excluded
10680 				node = nodes[0];
10681 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
10682 					nodes.splice(0, 1);
10683 				}
10684 
10685 				// Last node is excluded
10686 				node = nodes[nodes.length - 1];
10687 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
10688 					nodes.splice(nodes.length - 1, 1);
10689 				}
10690 
10691 				return nodes;
10692 			};
10693 
10694 			function collectSiblings(node, name, end_node) {
10695 				var siblings = [];
10696 
10697 				for (; node && node != end_node; node = node[name])
10698 					siblings.push(node);
10699 
10700 				return siblings;
10701 			};
10702 
10703 			function findEndPoint(node, root) {
10704 				do {
10705 					if (node.parentNode == root)
10706 						return node;
10707 
10708 					node = node.parentNode;
10709 				} while(node);
10710 			};
10711 
10712 			function walkBoundary(start_node, end_node, next) {
10713 				var siblingName = next ? 'nextSibling' : 'previousSibling';
10714 
10715 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
10716 					parent = node.parentNode;
10717 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
10718 
10719 					if (siblings.length) {
10720 						if (!next)
10721 							siblings.reverse();
10722 
10723 						callback(exclude(siblings));
10724 					}
10725 				}
10726 			};
10727 
10728 			// If index based start position then resolve it
10729 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
10730 				startContainer = startContainer.childNodes[startOffset];
10731 
10732 			// If index based end position then resolve it
10733 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
10734 				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
10735 
10736 			// Same container
10737 			if (startContainer == endContainer)
10738 				return callback(exclude([startContainer]));
10739 
10740 			// Find common ancestor and end points
10741 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
10742 				
10743 			// Process left side
10744 			for (node = startContainer; node; node = node.parentNode) {
10745 				if (node === endContainer)
10746 					return walkBoundary(startContainer, ancestor, true);
10747 
10748 				if (node === ancestor)
10749 					break;
10750 			}
10751 
10752 			// Process right side
10753 			for (node = endContainer; node; node = node.parentNode) {
10754 				if (node === startContainer)
10755 					return walkBoundary(endContainer, ancestor);
10756 
10757 				if (node === ancestor)
10758 					break;
10759 			}
10760 
10761 			// Find start/end point
10762 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
10763 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
10764 
10765 			// Walk left leaf
10766 			walkBoundary(startContainer, startPoint, true);
10767 
10768 			// Walk the middle from start to end point
10769 			siblings = collectSiblings(
10770 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
10771 				'nextSibling',
10772 				endPoint == endContainer ? endPoint.nextSibling : endPoint
10773 			);
10774 
10775 			if (siblings.length)
10776 				callback(exclude(siblings));
10777 
10778 			// Walk right leaf
10779 			walkBoundary(endContainer, endPoint);
10780 		};
10781 
10782 		this.split = function(rng) {
10783 			var startContainer = rng.startContainer,
10784 				startOffset = rng.startOffset,
10785 				endContainer = rng.endContainer,
10786 				endOffset = rng.endOffset;
10787 
10788 			function splitText(node, offset) {
10789 				return node.splitText(offset);
10790 			};
10791 
10792 			// Handle single text node
10793 			if (startContainer == endContainer && startContainer.nodeType == 3) {
10794 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10795 					endContainer = splitText(startContainer, startOffset);
10796 					startContainer = endContainer.previousSibling;
10797 
10798 					if (endOffset > startOffset) {
10799 						endOffset = endOffset - startOffset;
10800 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
10801 						endOffset = endContainer.nodeValue.length;
10802 						startOffset = 0;
10803 					} else {
10804 						endOffset = 0;
10805 					}
10806 				}
10807 			} else {
10808 				// Split startContainer text node if needed
10809 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10810 					startContainer = splitText(startContainer, startOffset);
10811 					startOffset = 0;
10812 				}
10813 
10814 				// Split endContainer text node if needed
10815 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
10816 					endContainer = splitText(endContainer, endOffset).previousSibling;
10817 					endOffset = endContainer.nodeValue.length;
10818 				}
10819 			}
10820 
10821 			return {
10822 				startContainer : startContainer,
10823 				startOffset : startOffset,
10824 				endContainer : endContainer,
10825 				endOffset : endOffset
10826 			};
10827 		};
10828 
10829 	};
10830 
10831 	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
10832 		if (rng1 && rng2) {
10833 			// Compare native IE ranges
10834 			if (rng1.item || rng1.duplicate) {
10835 				// Both are control ranges and the selected element matches
10836 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
10837 					return true;
10838 
10839 				// Both are text ranges and the range matches
10840 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
10841 					return true;
10842 			} else {
10843 				// Compare w3c ranges
10844 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
10845 			}
10846 		}
10847 
10848 		return false;
10849 	};
10850 })(tinymce);
10851 
10852 (function(tinymce) {
10853 	var Event = tinymce.dom.Event, each = tinymce.each;
10854 
10855 	tinymce.create('tinymce.ui.KeyboardNavigation', {
10856 		KeyboardNavigation: function(settings, dom) {
10857 			var t = this, root = settings.root, items = settings.items,
10858 					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
10859 					excludeFromTabOrder = settings.excludeFromTabOrder,
10860 					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
10861 
10862 			dom = dom || tinymce.DOM;
10863 
10864 			itemFocussed = function(evt) {
10865 				focussedId = evt.target.id;
10866 			};
10867 			
10868 			itemBlurred = function(evt) {
10869 				dom.setAttrib(evt.target.id, 'tabindex', '-1');
10870 			};
10871 			
10872 			rootFocussed = function(evt) {
10873 				var item = dom.get(focussedId);
10874 				dom.setAttrib(item, 'tabindex', '0');
10875 				item.focus();
10876 			};
10877 			
10878 			t.focus = function() {
10879 				dom.get(focussedId).focus();
10880 			};
10881 
10882 			t.destroy = function() {
10883 				each(items, function(item) {
10884 					var elm = dom.get(item.id);
10885 
10886 					dom.unbind(elm, 'focus', itemFocussed);
10887 					dom.unbind(elm, 'blur', itemBlurred);
10888 				});
10889 
10890 				var rootElm = dom.get(root);
10891 				dom.unbind(rootElm, 'focus', rootFocussed);
10892 				dom.unbind(rootElm, 'keydown', rootKeydown);
10893 
10894 				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
10895 				t.destroy = function() {};
10896 			};
10897 			
10898 			t.moveFocus = function(dir, evt) {
10899 				var idx = -1, controls = t.controls, newFocus;
10900 
10901 				if (!focussedId)
10902 					return;
10903 
10904 				each(items, function(item, index) {
10905 					if (item.id === focussedId) {
10906 						idx = index;
10907 						return false;
10908 					}
10909 				});
10910 
10911 				idx += dir;
10912 				if (idx < 0) {
10913 					idx = items.length - 1;
10914 				} else if (idx >= items.length) {
10915 					idx = 0;
10916 				}
10917 				
10918 				newFocus = items[idx];
10919 				dom.setAttrib(focussedId, 'tabindex', '-1');
10920 				dom.setAttrib(newFocus.id, 'tabindex', '0');
10921 				dom.get(newFocus.id).focus();
10922 
10923 				if (settings.actOnFocus) {
10924 					settings.onAction(newFocus.id);
10925 				}
10926 
10927 				if (evt)
10928 					Event.cancel(evt);
10929 			};
10930 			
10931 			rootKeydown = function(evt) {
10932 				var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
10933 				
10934 				switch (evt.keyCode) {
10935 					case DOM_VK_LEFT:
10936 						if (enableLeftRight) t.moveFocus(-1);
10937 						break;
10938 	
10939 					case DOM_VK_RIGHT:
10940 						if (enableLeftRight) t.moveFocus(1);
10941 						break;
10942 	
10943 					case DOM_VK_UP:
10944 						if (enableUpDown) t.moveFocus(-1);
10945 						break;
10946 
10947 					case DOM_VK_DOWN:
10948 						if (enableUpDown) t.moveFocus(1);
10949 						break;
10950 
10951 					case DOM_VK_ESCAPE:
10952 						if (settings.onCancel) {
10953 							settings.onCancel();
10954 							Event.cancel(evt);
10955 						}
10956 						break;
10957 
10958 					case DOM_VK_ENTER:
10959 					case DOM_VK_RETURN:
10960 					case DOM_VK_SPACE:
10961 						if (settings.onAction) {
10962 							settings.onAction(focussedId);
10963 							Event.cancel(evt);
10964 						}
10965 						break;
10966 				}
10967 			};
10968 
10969 			// Set up state and listeners for each item.
10970 			each(items, function(item, idx) {
10971 				var tabindex, elm;
10972 
10973 				if (!item.id) {
10974 					item.id = dom.uniqueId('_mce_item_');
10975 				}
10976 
10977 				elm = dom.get(item.id);
10978 
10979 				if (excludeFromTabOrder) {
10980 					dom.bind(elm, 'blur', itemBlurred);
10981 					tabindex = '-1';
10982 				} else {
10983 					tabindex = (idx === 0 ? '0' : '-1');
10984 				}
10985 
10986 				elm.setAttribute('tabindex', tabindex);
10987 				dom.bind(elm, 'focus', itemFocussed);
10988 			});
10989 			
10990 			// Setup initial state for root element.
10991 			if (items[0]){
10992 				focussedId = items[0].id;
10993 			}
10994 
10995 			dom.setAttrib(root, 'tabindex', '-1');
10996 
10997 			// Setup listeners for root element.
10998 			var rootElm = dom.get(root);
10999 			dom.bind(rootElm, 'focus', rootFocussed);
11000 			dom.bind(rootElm, 'keydown', rootKeydown);
11001 		}
11002 	});
11003 })(tinymce);
11004 
11005 (function(tinymce) {
11006 	// Shorten class names
11007 	var DOM = tinymce.DOM, is = tinymce.is;
11008 
11009 	tinymce.create('tinymce.ui.Control', {
11010 		Control : function(id, s, editor) {
11011 			this.id = id;
11012 			this.settings = s = s || {};
11013 			this.rendered = false;
11014 			this.onRender = new tinymce.util.Dispatcher(this);
11015 			this.classPrefix = '';
11016 			this.scope = s.scope || this;
11017 			this.disabled = 0;
11018 			this.active = 0;
11019 			this.editor = editor;
11020 		},
11021 		
11022 		setAriaProperty : function(property, value) {
11023 			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
11024 			if (element) {
11025 				DOM.setAttrib(element, 'aria-' + property, !!value);
11026 			}
11027 		},
11028 		
11029 		focus : function() {
11030 			DOM.get(this.id).focus();
11031 		},
11032 
11033 		setDisabled : function(s) {
11034 			if (s != this.disabled) {
11035 				this.setAriaProperty('disabled', s);
11036 
11037 				this.setState('Disabled', s);
11038 				this.setState('Enabled', !s);
11039 				this.disabled = s;
11040 			}
11041 		},
11042 
11043 		isDisabled : function() {
11044 			return this.disabled;
11045 		},
11046 
11047 		setActive : function(s) {
11048 			if (s != this.active) {
11049 				this.setState('Active', s);
11050 				this.active = s;
11051 				this.setAriaProperty('pressed', s);
11052 			}
11053 		},
11054 
11055 		isActive : function() {
11056 			return this.active;
11057 		},
11058 
11059 		setState : function(c, s) {
11060 			var n = DOM.get(this.id);
11061 
11062 			c = this.classPrefix + c;
11063 
11064 			if (s)
11065 				DOM.addClass(n, c);
11066 			else
11067 				DOM.removeClass(n, c);
11068 		},
11069 
11070 		isRendered : function() {
11071 			return this.rendered;
11072 		},
11073 
11074 		renderHTML : function() {
11075 		},
11076 
11077 		renderTo : function(n) {
11078 			DOM.setHTML(n, this.renderHTML());
11079 		},
11080 
11081 		postRender : function() {
11082 			var t = this, b;
11083 
11084 			// Set pending states
11085 			if (is(t.disabled)) {
11086 				b = t.disabled;
11087 				t.disabled = -1;
11088 				t.setDisabled(b);
11089 			}
11090 
11091 			if (is(t.active)) {
11092 				b = t.active;
11093 				t.active = -1;
11094 				t.setActive(b);
11095 			}
11096 		},
11097 
11098 		remove : function() {
11099 			DOM.remove(this.id);
11100 			this.destroy();
11101 		},
11102 
11103 		destroy : function() {
11104 			tinymce.dom.Event.clear(this.id);
11105 		}
11106 	});
11107 })(tinymce);
11108 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
11109 	Container : function(id, s, editor) {
11110 		this.parent(id, s, editor);
11111 
11112 		this.controls = [];
11113 
11114 		this.lookup = {};
11115 	},
11116 
11117 	add : function(c) {
11118 		this.lookup[c.id] = c;
11119 		this.controls.push(c);
11120 
11121 		return c;
11122 	},
11123 
11124 	get : function(n) {
11125 		return this.lookup[n];
11126 	}
11127 });
11128 
11129 
11130 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11131 	Separator : function(id, s) {
11132 		this.parent(id, s);
11133 		this.classPrefix = 'mceSeparator';
11134 		this.setDisabled(true);
11135 	},
11136 
11137 	renderHTML : function() {
11138 		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
11139 	}
11140 });
11141 
11142 (function(tinymce) {
11143 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11144 
11145 	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
11146 		MenuItem : function(id, s) {
11147 			this.parent(id, s);
11148 			this.classPrefix = 'mceMenuItem';
11149 		},
11150 
11151 		setSelected : function(s) {
11152 			this.setState('Selected', s);
11153 			this.setAriaProperty('checked', !!s);
11154 			this.selected = s;
11155 		},
11156 
11157 		isSelected : function() {
11158 			return this.selected;
11159 		},
11160 
11161 		postRender : function() {
11162 			var t = this;
11163 			
11164 			t.parent();
11165 
11166 			// Set pending state
11167 			if (is(t.selected))
11168 				t.setSelected(t.selected);
11169 		}
11170 	});
11171 })(tinymce);
11172 
11173 (function(tinymce) {
11174 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11175 
11176 	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
11177 		Menu : function(id, s) {
11178 			var t = this;
11179 
11180 			t.parent(id, s);
11181 			t.items = {};
11182 			t.collapsed = false;
11183 			t.menuCount = 0;
11184 			t.onAddItem = new tinymce.util.Dispatcher(this);
11185 		},
11186 
11187 		expand : function(d) {
11188 			var t = this;
11189 
11190 			if (d) {
11191 				walk(t, function(o) {
11192 					if (o.expand)
11193 						o.expand();
11194 				}, 'items', t);
11195 			}
11196 
11197 			t.collapsed = false;
11198 		},
11199 
11200 		collapse : function(d) {
11201 			var t = this;
11202 
11203 			if (d) {
11204 				walk(t, function(o) {
11205 					if (o.collapse)
11206 						o.collapse();
11207 				}, 'items', t);
11208 			}
11209 
11210 			t.collapsed = true;
11211 		},
11212 
11213 		isCollapsed : function() {
11214 			return this.collapsed;
11215 		},
11216 
11217 		add : function(o) {
11218 			if (!o.settings)
11219 				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
11220 
11221 			this.onAddItem.dispatch(this, o);
11222 
11223 			return this.items[o.id] = o;
11224 		},
11225 
11226 		addSeparator : function() {
11227 			return this.add({separator : true});
11228 		},
11229 
11230 		addMenu : function(o) {
11231 			if (!o.collapse)
11232 				o = this.createMenu(o);
11233 
11234 			this.menuCount++;
11235 
11236 			return this.add(o);
11237 		},
11238 
11239 		hasMenus : function() {
11240 			return this.menuCount !== 0;
11241 		},
11242 
11243 		remove : function(o) {
11244 			delete this.items[o.id];
11245 		},
11246 
11247 		removeAll : function() {
11248 			var t = this;
11249 
11250 			walk(t, function(o) {
11251 				if (o.removeAll)
11252 					o.removeAll();
11253 				else
11254 					o.remove();
11255 
11256 				o.destroy();
11257 			}, 'items', t);
11258 
11259 			t.items = {};
11260 		},
11261 
11262 		createMenu : function(o) {
11263 			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
11264 
11265 			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
11266 
11267 			return m;
11268 		}
11269 	});
11270 })(tinymce);
11271 (function(tinymce) {
11272 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
11273 
11274 	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
11275 		DropMenu : function(id, s) {
11276 			s = s || {};
11277 			s.container = s.container || DOM.doc.body;
11278 			s.offset_x = s.offset_x || 0;
11279 			s.offset_y = s.offset_y || 0;
11280 			s.vp_offset_x = s.vp_offset_x || 0;
11281 			s.vp_offset_y = s.vp_offset_y || 0;
11282 
11283 			if (is(s.icons) && !s.icons)
11284 				s['class'] += ' mceNoIcons';
11285 
11286 			this.parent(id, s);
11287 			this.onShowMenu = new tinymce.util.Dispatcher(this);
11288 			this.onHideMenu = new tinymce.util.Dispatcher(this);
11289 			this.classPrefix = 'mceMenu';
11290 		},
11291 
11292 		createMenu : function(s) {
11293 			var t = this, cs = t.settings, m;
11294 
11295 			s.container = s.container || cs.container;
11296 			s.parent = t;
11297 			s.constrain = s.constrain || cs.constrain;
11298 			s['class'] = s['class'] || cs['class'];
11299 			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
11300 			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
11301 			s.keyboard_focus = cs.keyboard_focus;
11302 			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
11303 
11304 			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
11305 
11306 			return m;
11307 		},
11308 		
11309 		focus : function() {
11310 			var t = this;
11311 			if (t.keyboardNav) {
11312 				t.keyboardNav.focus();
11313 			}
11314 		},
11315 
11316 		update : function() {
11317 			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
11318 
11319 			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11320 			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
11321 
11322 			if (!DOM.boxModel)
11323 				t.element.setStyles({width : tw + 2, height : th + 2});
11324 			else
11325 				t.element.setStyles({width : tw, height : th});
11326 
11327 			if (s.max_width)
11328 				DOM.setStyle(co, 'width', tw);
11329 
11330 			if (s.max_height) {
11331 				DOM.setStyle(co, 'height', th);
11332 
11333 				if (tb.clientHeight < s.max_height)
11334 					DOM.setStyle(co, 'overflow', 'hidden');
11335 			}
11336 		},
11337 
11338 		showMenu : function(x, y, px) {
11339 			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
11340 
11341 			t.collapse(1);
11342 
11343 			if (t.isMenuVisible)
11344 				return;
11345 
11346 			if (!t.rendered) {
11347 				co = DOM.add(t.settings.container, t.renderNode());
11348 
11349 				each(t.items, function(o) {
11350 					o.postRender();
11351 				});
11352 
11353 				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11354 			} else
11355 				co = DOM.get('menu_' + t.id);
11356 
11357 			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
11358 			if (!tinymce.isOpera)
11359 				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
11360 
11361 			DOM.show(co);
11362 			t.update();
11363 
11364 			x += s.offset_x || 0;
11365 			y += s.offset_y || 0;
11366 			vp.w -= 4;
11367 			vp.h -= 4;
11368 
11369 			// Move inside viewport if not submenu
11370 			if (s.constrain) {
11371 				w = co.clientWidth - ot;
11372 				h = co.clientHeight - ot;
11373 				mx = vp.x + vp.w;
11374 				my = vp.y + vp.h;
11375 
11376 				if ((x + s.vp_offset_x + w) > mx)
11377 					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
11378 
11379 				if ((y + s.vp_offset_y + h) > my)
11380 					y = Math.max(0, (my - s.vp_offset_y) - h);
11381 			}
11382 
11383 			DOM.setStyles(co, {left : x , top : y});
11384 			t.element.update();
11385 
11386 			t.isMenuVisible = 1;
11387 			t.mouseClickFunc = Event.add(co, 'click', function(e) {
11388 				var m;
11389 
11390 				e = e.target;
11391 
11392 				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
11393 					m = t.items[e.id];
11394 
11395 					if (m.isDisabled())
11396 						return;
11397 
11398 					dm = t;
11399 
11400 					while (dm) {
11401 						if (dm.hideMenu)
11402 							dm.hideMenu();
11403 
11404 						dm = dm.settings.parent;
11405 					}
11406 
11407 					if (m.settings.onclick)
11408 						m.settings.onclick(e);
11409 
11410 					return false; // Cancel to fix onbeforeunload problem
11411 				}
11412 			});
11413 
11414 			if (t.hasMenus()) {
11415 				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
11416 					var m, r, mi;
11417 
11418 					e = e.target;
11419 					if (e && (e = DOM.getParent(e, 'tr'))) {
11420 						m = t.items[e.id];
11421 
11422 						if (t.lastMenu)
11423 							t.lastMenu.collapse(1);
11424 
11425 						if (m.isDisabled())
11426 							return;
11427 
11428 						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
11429 							//p = DOM.getPos(s.container);
11430 							r = DOM.getRect(e);
11431 							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
11432 							t.lastMenu = m;
11433 							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
11434 						}
11435 					}
11436 				});
11437 			}
11438 			
11439 			Event.add(co, 'keydown', t._keyHandler, t);
11440 
11441 			t.onShowMenu.dispatch(t);
11442 
11443 			if (s.keyboard_focus) { 
11444 				t._setupKeyboardNav(); 
11445 			}
11446 		},
11447 
11448 		hideMenu : function(c) {
11449 			var t = this, co = DOM.get('menu_' + t.id), e;
11450 
11451 			if (!t.isMenuVisible)
11452 				return;
11453 
11454 			if (t.keyboardNav) t.keyboardNav.destroy();
11455 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11456 			Event.remove(co, 'click', t.mouseClickFunc);
11457 			Event.remove(co, 'keydown', t._keyHandler);
11458 			DOM.hide(co);
11459 			t.isMenuVisible = 0;
11460 
11461 			if (!c)
11462 				t.collapse(1);
11463 
11464 			if (t.element)
11465 				t.element.hide();
11466 
11467 			if (e = DOM.get(t.id))
11468 				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
11469 
11470 			t.onHideMenu.dispatch(t);
11471 		},
11472 
11473 		add : function(o) {
11474 			var t = this, co;
11475 
11476 			o = t.parent(o);
11477 
11478 			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
11479 				t._add(DOM.select('tbody', co)[0], o);
11480 
11481 			return o;
11482 		},
11483 
11484 		collapse : function(d) {
11485 			this.parent(d);
11486 			this.hideMenu(1);
11487 		},
11488 
11489 		remove : function(o) {
11490 			DOM.remove(o.id);
11491 			this.destroy();
11492 
11493 			return this.parent(o);
11494 		},
11495 
11496 		destroy : function() {
11497 			var t = this, co = DOM.get('menu_' + t.id);
11498 
11499 			if (t.keyboardNav) t.keyboardNav.destroy();
11500 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11501 			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
11502 			Event.remove(co, 'click', t.mouseClickFunc);
11503 			Event.remove(co, 'keydown', t._keyHandler);
11504 
11505 			if (t.element)
11506 				t.element.remove();
11507 
11508 			DOM.remove(co);
11509 		},
11510 
11511 		renderNode : function() {
11512 			var t = this, s = t.settings, n, tb, co, w;
11513 
11514 			w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
11515 			if (t.settings.parent) {
11516 				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
11517 			}
11518 			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
11519 			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11520 
11521 			if (s.menu_line)
11522 				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
11523 
11524 //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
11525 			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
11526 			tb = DOM.add(n, 'tbody');
11527 
11528 			each(t.items, function(o) {
11529 				t._add(tb, o);
11530 			});
11531 
11532 			t.rendered = true;
11533 
11534 			return w;
11535 		},
11536 
11537 		// Internal functions
11538 		_setupKeyboardNav : function(){
11539 			var contextMenu, menuItems, t=this; 
11540 			contextMenu = DOM.get('menu_' + t.id);
11541 			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
11542 			menuItems.splice(0,0,contextMenu);
11543 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11544 				root: 'menu_' + t.id,
11545 				items: menuItems,
11546 				onCancel: function() {
11547 					t.hideMenu();
11548 				},
11549 				enableUpDown: true
11550 			});
11551 			contextMenu.focus();
11552 		},
11553 
11554 		_keyHandler : function(evt) {
11555 			var t = this, e;
11556 			switch (evt.keyCode) {
11557 				case 37: // Left
11558 					if (t.settings.parent) {
11559 						t.hideMenu();
11560 						t.settings.parent.focus();
11561 						Event.cancel(evt);
11562 					}
11563 					break;
11564 				case 39: // Right
11565 					if (t.mouseOverFunc)
11566 						t.mouseOverFunc(evt);
11567 					break;
11568 			}
11569 		},
11570 
11571 		_add : function(tb, o) {
11572 			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
11573 
11574 			if (s.separator) {
11575 				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
11576 				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
11577 
11578 				if (n = ro.previousSibling)
11579 					DOM.addClass(n, 'mceLast');
11580 
11581 				return;
11582 			}
11583 
11584 			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
11585 			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
11586 			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
11587 
11588 			if (s.parent) {
11589 				DOM.setAttrib(a, 'aria-haspopup', 'true');
11590 				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
11591 			}
11592 
11593 			DOM.addClass(it, s['class']);
11594 //			n = DOM.add(n, 'span', {'class' : 'item'});
11595 
11596 			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
11597 
11598 			if (s.icon_src)
11599 				DOM.add(ic, 'img', {src : s.icon_src});
11600 
11601 			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
11602 
11603 			if (o.settings.style) {
11604 				if (typeof o.settings.style == "function")
11605 					o.settings.style = o.settings.style();
11606 
11607 				DOM.setAttrib(n, 'style', o.settings.style);
11608 			}
11609 
11610 			if (tb.childNodes.length == 1)
11611 				DOM.addClass(ro, 'mceFirst');
11612 
11613 			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
11614 				DOM.addClass(ro, 'mceFirst');
11615 
11616 			if (o.collapse)
11617 				DOM.addClass(ro, cp + 'ItemSub');
11618 
11619 			if (n = ro.previousSibling)
11620 				DOM.removeClass(n, 'mceLast');
11621 
11622 			DOM.addClass(ro, 'mceLast');
11623 		}
11624 	});
11625 })(tinymce);
11626 (function(tinymce) {
11627 	var DOM = tinymce.DOM;
11628 
11629 	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
11630 		Button : function(id, s, ed) {
11631 			this.parent(id, s, ed);
11632 			this.classPrefix = 'mceButton';
11633 		},
11634 
11635 		renderHTML : function() {
11636 			var cp = this.classPrefix, s = this.settings, h, l;
11637 
11638 			l = DOM.encode(s.label || '');
11639 			h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
11640 			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
11641 				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11642 			else
11643 				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11644 
11645 			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
11646 			h += '</a>';
11647 			return h;
11648 		},
11649 
11650 		postRender : function() {
11651 			var t = this, s = t.settings, imgBookmark;
11652 
11653 			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
11654 			// need to keep the selection in case the selection is lost
11655 			if (tinymce.isIE && t.editor) {
11656 				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
11657 					var nodeName = t.editor.selection.getNode().nodeName;
11658 					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
11659 				});
11660 			}
11661 			tinymce.dom.Event.add(t.id, 'click', function(e) {
11662 				if (!t.isDisabled()) {
11663 					// restore the selection in case the selection is lost in IE
11664 					if (tinymce.isIE && t.editor && imgBookmark !== null) {
11665 						t.editor.selection.moveToBookmark(imgBookmark);
11666 					}
11667 					return s.onclick.call(s.scope, e);
11668 				}
11669 			});
11670 			tinymce.dom.Event.add(t.id, 'keyup', function(e) {
11671 				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
11672 					return s.onclick.call(s.scope, e);
11673 			});
11674 		}
11675 	});
11676 })(tinymce);
11677 
11678 (function(tinymce) {
11679 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11680 
11681 	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
11682 		ListBox : function(id, s, ed) {
11683 			var t = this;
11684 
11685 			t.parent(id, s, ed);
11686 
11687 			t.items = [];
11688 
11689 			t.onChange = new Dispatcher(t);
11690 
11691 			t.onPostRender = new Dispatcher(t);
11692 
11693 			t.onAdd = new Dispatcher(t);
11694 
11695 			t.onRenderMenu = new tinymce.util.Dispatcher(this);
11696 
11697 			t.classPrefix = 'mceListBox';
11698 			t.marked = {};
11699 		},
11700 
11701 		select : function(va) {
11702 			var t = this, fv, f;
11703 
11704 			t.marked = {};
11705 
11706 			if (va == undef)
11707 				return t.selectByIndex(-1);
11708 
11709 			// Is string or number make function selector
11710 			if (va && typeof(va)=="function")
11711 				f = va;
11712 			else {
11713 				f = function(v) {
11714 					return v == va;
11715 				};
11716 			}
11717 
11718 			// Do we need to do something?
11719 			if (va != t.selectedValue) {
11720 				// Find item
11721 				each(t.items, function(o, i) {
11722 					if (f(o.value)) {
11723 						fv = 1;
11724 						t.selectByIndex(i);
11725 						return false;
11726 					}
11727 				});
11728 
11729 				if (!fv)
11730 					t.selectByIndex(-1);
11731 			}
11732 		},
11733 
11734 		selectByIndex : function(idx) {
11735 			var t = this, e, o, label;
11736 
11737 			t.marked = {};
11738 
11739 			if (idx != t.selectedIndex) {
11740 				e = DOM.get(t.id + '_text');
11741 				label = DOM.get(t.id + '_voiceDesc');
11742 				o = t.items[idx];
11743 
11744 				if (o) {
11745 					t.selectedValue = o.value;
11746 					t.selectedIndex = idx;
11747 					DOM.setHTML(e, DOM.encode(o.title));
11748 					DOM.setHTML(label, t.settings.title + " - " + o.title);
11749 					DOM.removeClass(e, 'mceTitle');
11750 					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
11751 				} else {
11752 					DOM.setHTML(e, DOM.encode(t.settings.title));
11753 					DOM.setHTML(label, DOM.encode(t.settings.title));
11754 					DOM.addClass(e, 'mceTitle');
11755 					t.selectedValue = t.selectedIndex = null;
11756 					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
11757 				}
11758 				e = 0;
11759 			}
11760 		},
11761 
11762 		mark : function(value) {
11763 			this.marked[value] = true;
11764 		},
11765 
11766 		add : function(n, v, o) {
11767 			var t = this;
11768 
11769 			o = o || {};
11770 			o = tinymce.extend(o, {
11771 				title : n,
11772 				value : v
11773 			});
11774 
11775 			t.items.push(o);
11776 			t.onAdd.dispatch(t, o);
11777 		},
11778 
11779 		getLength : function() {
11780 			return this.items.length;
11781 		},
11782 
11783 		renderHTML : function() {
11784 			var h = '', t = this, s = t.settings, cp = t.classPrefix;
11785 
11786 			h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
11787 			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
11788 			h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
11789 			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
11790 			h += '</tr></tbody></table></span>';
11791 
11792 			return h;
11793 		},
11794 
11795 		showMenu : function() {
11796 			var t = this, p2, e = DOM.get(this.id), m;
11797 
11798 			if (t.isDisabled() || t.items.length === 0)
11799 				return;
11800 
11801 			if (t.menu && t.menu.isMenuVisible)
11802 				return t.hideMenu();
11803 
11804 			if (!t.isMenuRendered) {
11805 				t.renderMenu();
11806 				t.isMenuRendered = true;
11807 			}
11808 
11809 			p2 = DOM.getPos(e);
11810 
11811 			m = t.menu;
11812 			m.settings.offset_x = p2.x;
11813 			m.settings.offset_y = p2.y;
11814 			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
11815 
11816 			// Select in menu
11817 			each(t.items, function(o) {
11818 				if (m.items[o.id]) {
11819 					m.items[o.id].setSelected(0);
11820 				}
11821 			});
11822 
11823 			each(t.items, function(o) {
11824 				if (m.items[o.id] && t.marked[o.value]) {
11825 					m.items[o.id].setSelected(1);
11826 				}
11827 
11828 				if (o.value === t.selectedValue) {
11829 					m.items[o.id].setSelected(1);
11830 				}
11831 			});
11832 
11833 			m.showMenu(0, e.clientHeight);
11834 
11835 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11836 			DOM.addClass(t.id, t.classPrefix + 'Selected');
11837 
11838 			//DOM.get(t.id + '_text').focus();
11839 		},
11840 
11841 		hideMenu : function(e) {
11842 			var t = this;
11843 
11844 			if (t.menu && t.menu.isMenuVisible) {
11845 				DOM.removeClass(t.id, t.classPrefix + 'Selected');
11846 
11847 				// Prevent double toogles by canceling the mouse click event to the button
11848 				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
11849 					return;
11850 
11851 				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
11852 					DOM.removeClass(t.id, t.classPrefix + 'Selected');
11853 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11854 					t.menu.hideMenu();
11855 				}
11856 			}
11857 		},
11858 
11859 		renderMenu : function() {
11860 			var t = this, m;
11861 
11862 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
11863 				menu_line : 1,
11864 				'class' : t.classPrefix + 'Menu mceNoIcons',
11865 				max_width : 250,
11866 				max_height : 150
11867 			});
11868 
11869 			m.onHideMenu.add(function() {
11870 				t.hideMenu();
11871 				t.focus();
11872 			});
11873 
11874 			m.add({
11875 				title : t.settings.title,
11876 				'class' : 'mceMenuItemTitle',
11877 				onclick : function() {
11878 					if (t.settings.onselect('') !== false)
11879 						t.select(''); // Must be runned after
11880 				}
11881 			});
11882 
11883 			each(t.items, function(o) {
11884 				// No value then treat it as a title
11885 				if (o.value === undef) {
11886 					m.add({
11887 						title : o.title,
11888 						role : "option",
11889 						'class' : 'mceMenuItemTitle',
11890 						onclick : function() {
11891 							if (t.settings.onselect('') !== false)
11892 								t.select(''); // Must be runned after
11893 						}
11894 					});
11895 				} else {
11896 					o.id = DOM.uniqueId();
11897 					o.role= "option";
11898 					o.onclick = function() {
11899 						if (t.settings.onselect(o.value) !== false)
11900 							t.select(o.value); // Must be runned after
11901 					};
11902 
11903 					m.add(o);
11904 				}
11905 			});
11906 
11907 			t.onRenderMenu.dispatch(t, m);
11908 			t.menu = m;
11909 		},
11910 
11911 		postRender : function() {
11912 			var t = this, cp = t.classPrefix;
11913 
11914 			Event.add(t.id, 'click', t.showMenu, t);
11915 			Event.add(t.id, 'keydown', function(evt) {
11916 				if (evt.keyCode == 32) { // Space
11917 					t.showMenu(evt);
11918 					Event.cancel(evt);
11919 				}
11920 			});
11921 			Event.add(t.id, 'focus', function() {
11922 				if (!t._focused) {
11923 					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
11924 						if (e.keyCode == 40) {
11925 							t.showMenu();
11926 							Event.cancel(e);
11927 						}
11928 					});
11929 					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
11930 						var v;
11931 						if (e.keyCode == 13) {
11932 							// Fake select on enter
11933 							v = t.selectedValue;
11934 							t.selectedValue = null; // Needs to be null to fake change
11935 							Event.cancel(e);
11936 							t.settings.onselect(v);
11937 						}
11938 					});
11939 				}
11940 
11941 				t._focused = 1;
11942 			});
11943 			Event.add(t.id, 'blur', function() {
11944 				Event.remove(t.id, 'keydown', t.keyDownHandler);
11945 				Event.remove(t.id, 'keypress', t.keyPressHandler);
11946 				t._focused = 0;
11947 			});
11948 
11949 			// Old IE doesn't have hover on all elements
11950 			if (tinymce.isIE6 || !DOM.boxModel) {
11951 				Event.add(t.id, 'mouseover', function() {
11952 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11953 						DOM.addClass(t.id, cp + 'Hover');
11954 				});
11955 
11956 				Event.add(t.id, 'mouseout', function() {
11957 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11958 						DOM.removeClass(t.id, cp + 'Hover');
11959 				});
11960 			}
11961 
11962 			t.onPostRender.dispatch(t, DOM.get(t.id));
11963 		},
11964 
11965 		destroy : function() {
11966 			this.parent();
11967 
11968 			Event.clear(this.id + '_text');
11969 			Event.clear(this.id + '_open');
11970 		}
11971 	});
11972 })(tinymce);
11973 
11974 (function(tinymce) {
11975 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11976 
11977 	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
11978 		NativeListBox : function(id, s) {
11979 			this.parent(id, s);
11980 			this.classPrefix = 'mceNativeListBox';
11981 		},
11982 
11983 		setDisabled : function(s) {
11984 			DOM.get(this.id).disabled = s;
11985 			this.setAriaProperty('disabled', s);
11986 		},
11987 
11988 		isDisabled : function() {
11989 			return DOM.get(this.id).disabled;
11990 		},
11991 
11992 		select : function(va) {
11993 			var t = this, fv, f;
11994 
11995 			if (va == undef)
11996 				return t.selectByIndex(-1);
11997 
11998 			// Is string or number make function selector
11999 			if (va && typeof(va)=="function")
12000 				f = va;
12001 			else {
12002 				f = function(v) {
12003 					return v == va;
12004 				};
12005 			}
12006 
12007 			// Do we need to do something?
12008 			if (va != t.selectedValue) {
12009 				// Find item
12010 				each(t.items, function(o, i) {
12011 					if (f(o.value)) {
12012 						fv = 1;
12013 						t.selectByIndex(i);
12014 						return false;
12015 					}
12016 				});
12017 
12018 				if (!fv)
12019 					t.selectByIndex(-1);
12020 			}
12021 		},
12022 
12023 		selectByIndex : function(idx) {
12024 			DOM.get(this.id).selectedIndex = idx + 1;
12025 			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
12026 		},
12027 
12028 		add : function(n, v, a) {
12029 			var o, t = this;
12030 
12031 			a = a || {};
12032 			a.value = v;
12033 
12034 			if (t.isRendered())
12035 				DOM.add(DOM.get(this.id), 'option', a, n);
12036 
12037 			o = {
12038 				title : n,
12039 				value : v,
12040 				attribs : a
12041 			};
12042 
12043 			t.items.push(o);
12044 			t.onAdd.dispatch(t, o);
12045 		},
12046 
12047 		getLength : function() {
12048 			return this.items.length;
12049 		},
12050 
12051 		renderHTML : function() {
12052 			var h, t = this;
12053 
12054 			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
12055 
12056 			each(t.items, function(it) {
12057 				h += DOM.createHTML('option', {value : it.value}, it.title);
12058 			});
12059 
12060 			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
12061 			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
12062 			return h;
12063 		},
12064 
12065 		postRender : function() {
12066 			var t = this, ch, changeListenerAdded = true;
12067 
12068 			t.rendered = true;
12069 
12070 			function onChange(e) {
12071 				var v = t.items[e.target.selectedIndex - 1];
12072 
12073 				if (v && (v = v.value)) {
12074 					t.onChange.dispatch(t, v);
12075 
12076 					if (t.settings.onselect)
12077 						t.settings.onselect(v);
12078 				}
12079 			};
12080 
12081 			Event.add(t.id, 'change', onChange);
12082 
12083 			// Accessibility keyhandler
12084 			Event.add(t.id, 'keydown', function(e) {
12085 				var bf;
12086 
12087 				Event.remove(t.id, 'change', ch);
12088 				changeListenerAdded = false;
12089 
12090 				bf = Event.add(t.id, 'blur', function() {
12091 					if (changeListenerAdded) return;
12092 					changeListenerAdded = true;
12093 					Event.add(t.id, 'change', onChange);
12094 					Event.remove(t.id, 'blur', bf);
12095 				});
12096 
12097 				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
12098 				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
12099 					return Event.prevent(e);
12100 				}
12101 				
12102 				if (e.keyCode == 13 || e.keyCode == 32) {
12103 					onChange(e);
12104 					return Event.cancel(e);
12105 				}
12106 			});
12107 
12108 			t.onPostRender.dispatch(t, DOM.get(t.id));
12109 		}
12110 	});
12111 })(tinymce);
12112 
12113 (function(tinymce) {
12114 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12115 
12116 	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
12117 		MenuButton : function(id, s, ed) {
12118 			this.parent(id, s, ed);
12119 
12120 			this.onRenderMenu = new tinymce.util.Dispatcher(this);
12121 
12122 			s.menu_container = s.menu_container || DOM.doc.body;
12123 		},
12124 
12125 		showMenu : function() {
12126 			var t = this, p1, p2, e = DOM.get(t.id), m;
12127 
12128 			if (t.isDisabled())
12129 				return;
12130 
12131 			if (!t.isMenuRendered) {
12132 				t.renderMenu();
12133 				t.isMenuRendered = true;
12134 			}
12135 
12136 			if (t.isMenuVisible)
12137 				return t.hideMenu();
12138 
12139 			p1 = DOM.getPos(t.settings.menu_container);
12140 			p2 = DOM.getPos(e);
12141 
12142 			m = t.menu;
12143 			m.settings.offset_x = p2.x;
12144 			m.settings.offset_y = p2.y;
12145 			m.settings.vp_offset_x = p2.x;
12146 			m.settings.vp_offset_y = p2.y;
12147 			m.settings.keyboard_focus = t._focused;
12148 			m.showMenu(0, e.firstChild.clientHeight);
12149 
12150 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12151 			t.setState('Selected', 1);
12152 
12153 			t.isMenuVisible = 1;
12154 		},
12155 
12156 		renderMenu : function() {
12157 			var t = this, m;
12158 
12159 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12160 				menu_line : 1,
12161 				'class' : this.classPrefix + 'Menu',
12162 				icons : t.settings.icons
12163 			});
12164 
12165 			m.onHideMenu.add(function() {
12166 				t.hideMenu();
12167 				t.focus();
12168 			});
12169 
12170 			t.onRenderMenu.dispatch(t, m);
12171 			t.menu = m;
12172 		},
12173 
12174 		hideMenu : function(e) {
12175 			var t = this;
12176 
12177 			// Prevent double toogles by canceling the mouse click event to the button
12178 			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
12179 				return;
12180 
12181 			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12182 				t.setState('Selected', 0);
12183 				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12184 				if (t.menu)
12185 					t.menu.hideMenu();
12186 			}
12187 
12188 			t.isMenuVisible = 0;
12189 		},
12190 
12191 		postRender : function() {
12192 			var t = this, s = t.settings;
12193 
12194 			Event.add(t.id, 'click', function() {
12195 				if (!t.isDisabled()) {
12196 					if (s.onclick)
12197 						s.onclick(t.value);
12198 
12199 					t.showMenu();
12200 				}
12201 			});
12202 		}
12203 	});
12204 })(tinymce);
12205 
12206 (function(tinymce) {
12207 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12208 
12209 	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
12210 		SplitButton : function(id, s, ed) {
12211 			this.parent(id, s, ed);
12212 			this.classPrefix = 'mceSplitButton';
12213 		},
12214 
12215 		renderHTML : function() {
12216 			var h, t = this, s = t.settings, h1;
12217 
12218 			h = '<tbody><tr>';
12219 
12220 			if (s.image)
12221 				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
12222 			else
12223 				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
12224 
12225 			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
12226 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12227 	
12228 			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
12229 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12230 
12231 			h += '</tr></tbody>';
12232 			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
12233 			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
12234 		},
12235 
12236 		postRender : function() {
12237 			var t = this, s = t.settings, activate;
12238 
12239 			if (s.onclick) {
12240 				activate = function(evt) {
12241 					if (!t.isDisabled()) {
12242 						s.onclick(t.value);
12243 						Event.cancel(evt);
12244 					}
12245 				};
12246 				Event.add(t.id + '_action', 'click', activate);
12247 				Event.add(t.id, ['click', 'keydown'], function(evt) {
12248 					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
12249 					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
12250 						activate();
12251 						Event.cancel(evt);
12252 					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
12253 						t.showMenu();
12254 						Event.cancel(evt);
12255 					}
12256 				});
12257 			}
12258 
12259 			Event.add(t.id + '_open', 'click', function (evt) {
12260 				t.showMenu();
12261 				Event.cancel(evt);
12262 			});
12263 			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
12264 			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
12265 
12266 			// Old IE doesn't have hover on all elements
12267 			if (tinymce.isIE6 || !DOM.boxModel) {
12268 				Event.add(t.id, 'mouseover', function() {
12269 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12270 						DOM.addClass(t.id, 'mceSplitButtonHover');
12271 				});
12272 
12273 				Event.add(t.id, 'mouseout', function() {
12274 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12275 						DOM.removeClass(t.id, 'mceSplitButtonHover');
12276 				});
12277 			}
12278 		},
12279 
12280 		destroy : function() {
12281 			this.parent();
12282 
12283 			Event.clear(this.id + '_action');
12284 			Event.clear(this.id + '_open');
12285 			Event.clear(this.id);
12286 		}
12287 	});
12288 })(tinymce);
12289 
12290 (function(tinymce) {
12291 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
12292 
12293 	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
12294 		ColorSplitButton : function(id, s, ed) {
12295 			var t = this;
12296 
12297 			t.parent(id, s, ed);
12298 
12299 			t.settings = s = tinymce.extend({
12300 				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
12301 				grid_width : 8,
12302 				default_color : '#888888'
12303 			}, t.settings);
12304 
12305 			t.onShowMenu = new tinymce.util.Dispatcher(t);
12306 
12307 			t.onHideMenu = new tinymce.util.Dispatcher(t);
12308 
12309 			t.value = s.default_color;
12310 		},
12311 
12312 		showMenu : function() {
12313 			var t = this, r, p, e, p2;
12314 
12315 			if (t.isDisabled())
12316 				return;
12317 
12318 			if (!t.isMenuRendered) {
12319 				t.renderMenu();
12320 				t.isMenuRendered = true;
12321 			}
12322 
12323 			if (t.isMenuVisible)
12324 				return t.hideMenu();
12325 
12326 			e = DOM.get(t.id);
12327 			DOM.show(t.id + '_menu');
12328 			DOM.addClass(e, 'mceSplitButtonSelected');
12329 			p2 = DOM.getPos(e);
12330 			DOM.setStyles(t.id + '_menu', {
12331 				left : p2.x,
12332 				top : p2.y + e.firstChild.clientHeight,
12333 				zIndex : 200000
12334 			});
12335 			e = 0;
12336 
12337 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12338 			t.onShowMenu.dispatch(t);
12339 
12340 			if (t._focused) {
12341 				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
12342 					if (e.keyCode == 27)
12343 						t.hideMenu();
12344 				});
12345 
12346 				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
12347 			}
12348 
12349 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
12350 				root: t.id + '_menu',
12351 				items: DOM.select('a', t.id + '_menu'),
12352 				onCancel: function() {
12353 					t.hideMenu();
12354 					t.focus();
12355 				}
12356 			});
12357 
12358 			t.keyboardNav.focus();
12359 			t.isMenuVisible = 1;
12360 		},
12361 
12362 		hideMenu : function(e) {
12363 			var t = this;
12364 
12365 			if (t.isMenuVisible) {
12366 				// Prevent double toogles by canceling the mouse click event to the button
12367 				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
12368 					return;
12369 
12370 				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
12371 					DOM.removeClass(t.id, 'mceSplitButtonSelected');
12372 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12373 					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
12374 					DOM.hide(t.id + '_menu');
12375 				}
12376 
12377 				t.isMenuVisible = 0;
12378 				t.onHideMenu.dispatch();
12379 				t.keyboardNav.destroy();
12380 			}
12381 		},
12382 
12383 		renderMenu : function() {
12384 			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
12385 
12386 			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
12387 			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
12388 			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
12389 
12390 			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
12391 			tb = DOM.add(n, 'tbody');
12392 
12393 			// Generate color grid
12394 			i = 0;
12395 			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
12396 				c = c.replace(/^#/, '');
12397 
12398 				if (!i--) {
12399 					tr = DOM.add(tb, 'tr');
12400 					i = s.grid_width - 1;
12401 				}
12402 
12403 				n = DOM.add(tr, 'td');
12404 				var settings = {
12405 					href : 'javascript:;',
12406 					style : {
12407 						backgroundColor : '#' + c
12408 					},
12409 					'title': t.editor.getLang('colors.' + c, c),
12410 					'data-mce-color' : '#' + c
12411 				};
12412 
12413 				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
12414 				if (!tinymce.isIE ) {
12415 					settings.role = 'option';
12416 				}
12417 
12418 				n = DOM.add(n, 'a', settings);
12419 
12420 				if (t.editor.forcedHighContrastMode) {
12421 					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
12422 					if (n.getContext && (context = n.getContext("2d"))) {
12423 						context.fillStyle = '#' + c;
12424 						context.fillRect(0, 0, 16, 16);
12425 					} else {
12426 						// No point leaving a canvas element around if it's not supported for drawing on anyway.
12427 						DOM.remove(n);
12428 					}
12429 				}
12430 			});
12431 
12432 			if (s.more_colors_func) {
12433 				n = DOM.add(tb, 'tr');
12434 				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
12435 				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
12436 
12437 				Event.add(n, 'click', function(e) {
12438 					s.more_colors_func.call(s.more_colors_scope || this);
12439 					return Event.cancel(e); // Cancel to fix onbeforeunload problem
12440 				});
12441 			}
12442 
12443 			DOM.addClass(m, 'mceColorSplitMenu');
12444 
12445 			// Prevent IE from scrolling and hindering click to occur #4019
12446 			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
12447 
12448 			Event.add(t.id + '_menu', 'click', function(e) {
12449 				var c;
12450 
12451 				e = DOM.getParent(e.target, 'a', tb);
12452 
12453 				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
12454 					t.setColor(c);
12455 
12456 				return false; // Prevent IE auto save warning
12457 			});
12458 
12459 			return w;
12460 		},
12461 
12462 		setColor : function(c) {
12463 			this.displayColor(c);
12464 			this.hideMenu();
12465 			this.settings.onselect(c);
12466 		},
12467 		
12468 		displayColor : function(c) {
12469 			var t = this;
12470 
12471 			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
12472 
12473 			t.value = c;
12474 		},
12475 
12476 		postRender : function() {
12477 			var t = this, id = t.id;
12478 
12479 			t.parent();
12480 			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
12481 			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
12482 		},
12483 
12484 		destroy : function() {
12485 			var self = this;
12486 
12487 			self.parent();
12488 
12489 			Event.clear(self.id + '_menu');
12490 			Event.clear(self.id + '_more');
12491 			DOM.remove(self.id + '_menu');
12492 
12493 			if (self.keyboardNav) {
12494 				self.keyboardNav.destroy();
12495 			}
12496 		}
12497 	});
12498 })(tinymce);
12499 
12500 (function(tinymce) {
12501 // Shorten class names
12502 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
12503 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
12504 	renderHTML : function() {
12505 		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
12506 
12507 		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
12508 		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
12509 		h.push("<span role='application'>");
12510 		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
12511 		each(controls, function(toolbar) {
12512 			h.push(toolbar.renderHTML());
12513 		});
12514 		h.push("</span>");
12515 		h.push('</div>');
12516 
12517 		return h.join('');
12518 	},
12519 	
12520 	focus : function() {
12521 		var t = this;
12522 		dom.get(t.id).focus();
12523 	},
12524 	
12525 	postRender : function() {
12526 		var t = this, items = [];
12527 
12528 		each(t.controls, function(toolbar) {
12529 			each (toolbar.controls, function(control) {
12530 				if (control.id) {
12531 					items.push(control);
12532 				}
12533 			});
12534 		});
12535 
12536 		t.keyNav = new tinymce.ui.KeyboardNavigation({
12537 			root: t.id,
12538 			items: items,
12539 			onCancel: function() {
12540 				//Move focus if webkit so that navigation back will read the item.
12541 				if (tinymce.isWebKit) {
12542 					dom.get(t.editor.id+"_ifr").focus();
12543 				}
12544 				t.editor.focus();
12545 			},
12546 			excludeFromTabOrder: !t.settings.tab_focus_toolbar
12547 		});
12548 	},
12549 	
12550 	destroy : function() {
12551 		var self = this;
12552 
12553 		self.parent();
12554 		self.keyNav.destroy();
12555 		Event.clear(self.id);
12556 	}
12557 });
12558 })(tinymce);
12559 
12560 (function(tinymce) {
12561 // Shorten class names
12562 var dom = tinymce.DOM, each = tinymce.each;
12563 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12564 	renderHTML : function() {
12565 		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
12566 
12567 		cl = t.controls;
12568 		for (i=0; i<cl.length; i++) {
12569 			// Get current control, prev control, next control and if the control is a list box or not
12570 			co = cl[i];
12571 			pr = cl[i - 1];
12572 			nx = cl[i + 1];
12573 
12574 			// Add toolbar start
12575 			if (i === 0) {
12576 				c = 'mceToolbarStart';
12577 
12578 				if (co.Button)
12579 					c += ' mceToolbarStartButton';
12580 				else if (co.SplitButton)
12581 					c += ' mceToolbarStartSplitButton';
12582 				else if (co.ListBox)
12583 					c += ' mceToolbarStartListBox';
12584 
12585 				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12586 			}
12587 
12588 			// Add toolbar end before list box and after the previous button
12589 			// This is to fix the o2k7 editor skins
12590 			if (pr && co.ListBox) {
12591 				if (pr.Button || pr.SplitButton)
12592 					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
12593 			}
12594 
12595 			// Render control HTML
12596 
12597 			// IE 8 quick fix, needed to propertly generate a hit area for anchors
12598 			if (dom.stdMode)
12599 				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
12600 			else
12601 				h += '<td>' + co.renderHTML() + '</td>';
12602 
12603 			// Add toolbar start after list box and before the next button
12604 			// This is to fix the o2k7 editor skins
12605 			if (nx && co.ListBox) {
12606 				if (nx.Button || nx.SplitButton)
12607 					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
12608 			}
12609 		}
12610 
12611 		c = 'mceToolbarEnd';
12612 
12613 		if (co.Button)
12614 			c += ' mceToolbarEndButton';
12615 		else if (co.SplitButton)
12616 			c += ' mceToolbarEndSplitButton';
12617 		else if (co.ListBox)
12618 			c += ' mceToolbarEndListBox';
12619 
12620 		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12621 
12622 		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
12623 	}
12624 });
12625 })(tinymce);
12626 
12627 (function(tinymce) {
12628 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
12629 
12630 	tinymce.create('tinymce.AddOnManager', {
12631 		AddOnManager : function() {
12632 			var self = this;
12633 
12634 			self.items = [];
12635 			self.urls = {};
12636 			self.lookup = {};
12637 			self.onAdd = new Dispatcher(self);
12638 		},
12639 
12640 		get : function(n) {
12641 			if (this.lookup[n]) {
12642 				return this.lookup[n].instance;
12643 			} else {
12644 				return undefined;
12645 			}
12646 		},
12647 
12648 		dependencies : function(n) {
12649 			var result;
12650 			if (this.lookup[n]) {
12651 				result = this.lookup[n].dependencies;
12652 			}
12653 			return result || [];
12654 		},
12655 
12656 		requireLangPack : function(n) {
12657 			var s = tinymce.settings;
12658 
12659 			if (s && s.language && s.language_load !== false)
12660 				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
12661 		},
12662 
12663 		add : function(id, o, dependencies) {
12664 			this.items.push(o);
12665 			this.lookup[id] = {instance:o, dependencies:dependencies};
12666 			this.onAdd.dispatch(this, id, o);
12667 
12668 			return o;
12669 		},
12670 		createUrl: function(baseUrl, dep) {
12671 			if (typeof dep === "object") {
12672 				return dep
12673 			} else {
12674 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
12675 			}
12676 		},
12677 
12678 		addComponents: function(pluginName, scripts) {
12679 			var pluginUrl = this.urls[pluginName];
12680 			tinymce.each(scripts, function(script){
12681 				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
12682 			});
12683 		},
12684 
12685 		load : function(n, u, cb, s) {
12686 			var t = this, url = u;
12687 
12688 			function loadDependencies() {
12689 				var dependencies = t.dependencies(n);
12690 				tinymce.each(dependencies, function(dep) {
12691 					var newUrl = t.createUrl(u, dep);
12692 					t.load(newUrl.resource, newUrl, undefined, undefined);
12693 				});
12694 				if (cb) {
12695 					if (s) {
12696 						cb.call(s);
12697 					} else {
12698 						cb.call(tinymce.ScriptLoader);
12699 					}
12700 				}
12701 			}
12702 
12703 			if (t.urls[n])
12704 				return;
12705 			if (typeof u === "object")
12706 				url = u.prefix + u.resource + u.suffix;
12707 
12708 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
12709 				url = tinymce.baseURL + '/' + url;
12710 
12711 			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
12712 
12713 			if (t.lookup[n]) {
12714 				loadDependencies();
12715 			} else {
12716 				tinymce.ScriptLoader.add(url, loadDependencies, s);
12717 			}
12718 		}
12719 	});
12720 
12721 	// Create plugin and theme managers
12722 	tinymce.PluginManager = new tinymce.AddOnManager();
12723 	tinymce.ThemeManager = new tinymce.AddOnManager();
12724 }(tinymce));
12725 
12726 (function(tinymce) {
12727 	// Shorten names
12728 	var each = tinymce.each, extend = tinymce.extend,
12729 		DOM = tinymce.DOM, Event = tinymce.dom.Event,
12730 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
12731 		explode = tinymce.explode,
12732 		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
12733 
12734 	// Setup some URLs where the editor API is located and where the document is
12735 	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
12736 	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
12737 		tinymce.documentBaseURL += '/';
12738 
12739 	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
12740 
12741 	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
12742 
12743 	// Add before unload listener
12744 	// This was required since IE was leaking memory if you added and removed beforeunload listeners
12745 	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
12746 	tinymce.onBeforeUnload = new Dispatcher(tinymce);
12747 
12748 	// Must be on window or IE will leak if the editor is placed in frame or iframe
12749 	Event.add(window, 'beforeunload', function(e) {
12750 		tinymce.onBeforeUnload.dispatch(tinymce, e);
12751 	});
12752 
12753 	tinymce.onAddEditor = new Dispatcher(tinymce);
12754 
12755 	tinymce.onRemoveEditor = new Dispatcher(tinymce);
12756 
12757 	tinymce.EditorManager = extend(tinymce, {
12758 		editors : [],
12759 
12760 		i18n : {},
12761 
12762 		activeEditor : null,
12763 
12764 		init : function(s) {
12765 			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
12766 
12767 			function createId(elm) {
12768 				var id = elm.id;
12769 	
12770 				// Use element id, or unique name or generate a unique id
12771 				if (!id) {
12772 					id = elm.name;
12773 	
12774 					if (id && !DOM.get(id)) {
12775 						id = elm.name;
12776 					} else {
12777 						// Generate unique name
12778 						id = DOM.uniqueId();
12779 					}
12780 
12781 					elm.setAttribute('id', id);
12782 				}
12783 
12784 				return id;
12785 			};
12786 
12787 			function execCallback(se, n, s) {
12788 				var f = se[n];
12789 
12790 				if (!f)
12791 					return;
12792 
12793 				if (tinymce.is(f, 'string')) {
12794 					s = f.replace(/\.\w+$/, '');
12795 					s = s ? tinymce.resolve(s) : 0;
12796 					f = tinymce.resolve(f);
12797 				}
12798 
12799 				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
12800 			};
12801 
12802 			function hasClass(n, c) {
12803 				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
12804 			};
12805 
12806 			t.settings = s;
12807 
12808 			// Legacy call
12809 			Event.bind(window, 'ready', function() {
12810 				var l, co;
12811 
12812 				execCallback(s, 'onpageload');
12813 
12814 				switch (s.mode) {
12815 					case "exact":
12816 						l = s.elements || '';
12817 
12818 						if(l.length > 0) {
12819 							each(explode(l), function(v) {
12820 								if (DOM.get(v)) {
12821 									ed = new tinymce.Editor(v, s);
12822 									el.push(ed);
12823 									ed.render(1);
12824 								} else {
12825 									each(document.forms, function(f) {
12826 										each(f.elements, function(e) {
12827 											if (e.name === v) {
12828 												v = 'mce_editor_' + instanceCounter++;
12829 												DOM.setAttrib(e, 'id', v);
12830 
12831 												ed = new tinymce.Editor(v, s);
12832 												el.push(ed);
12833 												ed.render(1);
12834 											}
12835 										});
12836 									});
12837 								}
12838 							});
12839 						}
12840 						break;
12841 
12842 					case "textareas":
12843 					case "specific_textareas":
12844 						each(DOM.select('textarea'), function(elm) {
12845 							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
12846 								return;
12847 
12848 							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
12849 								ed = new tinymce.Editor(createId(elm), s);
12850 								el.push(ed);
12851 								ed.render(1);
12852 							}
12853 						});
12854 						break;
12855 					
12856 					default:
12857 						if (s.types) {
12858 							// Process type specific selector
12859 							each(s.types, function(type) {
12860 								each(DOM.select(type.selector), function(elm) {
12861 									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
12862 									el.push(editor);
12863 									editor.render(1);
12864 								});
12865 							});
12866 						} else if (s.selector) {
12867 							// Process global selector
12868 							each(DOM.select(s.selector), function(elm) {
12869 								var editor = new tinymce.Editor(createId(elm), s);
12870 								el.push(editor);
12871 								editor.render(1);
12872 							});
12873 						}
12874 				}
12875 
12876 				// Call onInit when all editors are initialized
12877 				if (s.oninit) {
12878 					l = co = 0;
12879 
12880 					each(el, function(ed) {
12881 						co++;
12882 
12883 						if (!ed.initialized) {
12884 							// Wait for it
12885 							ed.onInit.add(function() {
12886 								l++;
12887 
12888 								// All done
12889 								if (l == co)
12890 									execCallback(s, 'oninit');
12891 							});
12892 						} else
12893 							l++;
12894 
12895 						// All done
12896 						if (l == co)
12897 							execCallback(s, 'oninit');					
12898 					});
12899 				}
12900 			});
12901 		},
12902 
12903 		get : function(id) {
12904 			if (id === undef)
12905 				return this.editors;
12906 
12907 			return this.editors[id];
12908 		},
12909 
12910 		getInstanceById : function(id) {
12911 			return this.get(id);
12912 		},
12913 
12914 		add : function(editor) {
12915 			var self = this, editors = self.editors;
12916 
12917 			// Add named and index editor instance
12918 			editors[editor.id] = editor;
12919 			editors.push(editor);
12920 
12921 			self._setActive(editor);
12922 			self.onAddEditor.dispatch(self, editor);
12923 
12924 
12925 			return editor;
12926 		},
12927 
12928 		remove : function(editor) {
12929 			var t = this, i, editors = t.editors;
12930 
12931 			// Not in the collection
12932 			if (!editors[editor.id])
12933 				return null;
12934 
12935 			delete editors[editor.id];
12936 
12937 			for (i = 0; i < editors.length; i++) {
12938 				if (editors[i] == editor) {
12939 					editors.splice(i, 1);
12940 					break;
12941 				}
12942 			}
12943 
12944 			// Select another editor since the active one was removed
12945 			if (t.activeEditor == editor)
12946 				t._setActive(editors[0]);
12947 
12948 			editor.destroy();
12949 			t.onRemoveEditor.dispatch(t, editor);
12950 
12951 			return editor;
12952 		},
12953 
12954 		execCommand : function(c, u, v) {
12955 			var t = this, ed = t.get(v), w;
12956 
12957 			function clr() {
12958 				ed.destroy();
12959 				w.detachEvent('onunload', clr);
12960 				w = w.tinyMCE = w.tinymce = null; // IE leak
12961 			};
12962 
12963 			// Manager commands
12964 			switch (c) {
12965 				case "mceFocus":
12966 					ed.focus();
12967 					return true;
12968 
12969 				case "mceAddEditor":
12970 				case "mceAddControl":
12971 					if (!t.get(v))
12972 						new tinymce.Editor(v, t.settings).render();
12973 
12974 					return true;
12975 
12976 				case "mceAddFrameControl":
12977 					w = v.window;
12978 
12979 					// Add tinyMCE global instance and tinymce namespace to specified window
12980 					w.tinyMCE = tinyMCE;
12981 					w.tinymce = tinymce;
12982 
12983 					tinymce.DOM.doc = w.document;
12984 					tinymce.DOM.win = w;
12985 
12986 					ed = new tinymce.Editor(v.element_id, v);
12987 					ed.render();
12988 
12989 					// Fix IE memory leaks
12990 					if (tinymce.isIE) {
12991 						w.attachEvent('onunload', clr);
12992 					}
12993 
12994 					v.page_window = null;
12995 
12996 					return true;
12997 
12998 				case "mceRemoveEditor":
12999 				case "mceRemoveControl":
13000 					if (ed)
13001 						ed.remove();
13002 
13003 					return true;
13004 
13005 				case 'mceToggleEditor':
13006 					if (!ed) {
13007 						t.execCommand('mceAddControl', 0, v);
13008 						return true;
13009 					}
13010 
13011 					if (ed.isHidden())
13012 						ed.show();
13013 					else
13014 						ed.hide();
13015 
13016 					return true;
13017 			}
13018 
13019 			// Run command on active editor
13020 			if (t.activeEditor)
13021 				return t.activeEditor.execCommand(c, u, v);
13022 
13023 			return false;
13024 		},
13025 
13026 		execInstanceCommand : function(id, c, u, v) {
13027 			var ed = this.get(id);
13028 
13029 			if (ed)
13030 				return ed.execCommand(c, u, v);
13031 
13032 			return false;
13033 		},
13034 
13035 		triggerSave : function() {
13036 			each(this.editors, function(e) {
13037 				e.save();
13038 			});
13039 		},
13040 
13041 		addI18n : function(p, o) {
13042 			var lo, i18n = this.i18n;
13043 
13044 			if (!tinymce.is(p, 'string')) {
13045 				each(p, function(o, lc) {
13046 					each(o, function(o, g) {
13047 						each(o, function(o, k) {
13048 							if (g === 'common')
13049 								i18n[lc + '.' + k] = o;
13050 							else
13051 								i18n[lc + '.' + g + '.' + k] = o;
13052 						});
13053 					});
13054 				});
13055 			} else {
13056 				each(o, function(o, k) {
13057 					i18n[p + '.' + k] = o;
13058 				});
13059 			}
13060 		},
13061 
13062 		// Private methods
13063 
13064 		_setActive : function(editor) {
13065 			this.selectedInstance = this.activeEditor = editor;
13066 		}
13067 	});
13068 })(tinymce);
13069 
13070 (function(tinymce) {
13071 	// Shorten these names
13072 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
13073 		each = tinymce.each, isGecko = tinymce.isGecko,
13074 		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
13075 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13076 		explode = tinymce.explode;
13077 
13078 	tinymce.create('tinymce.Editor', {
13079 		Editor : function(id, settings) {
13080 			var self = this, TRUE = true;
13081 
13082 			self.settings = settings = extend({
13083 				id : id,
13084 				language : 'en',
13085 				theme : 'advanced',
13086 				skin : 'default',
13087 				delta_width : 0,
13088 				delta_height : 0,
13089 				popup_css : '',
13090 				plugins : '',
13091 				document_base_url : tinymce.documentBaseURL,
13092 				add_form_submit_trigger : TRUE,
13093 				submit_patch : TRUE,
13094 				add_unload_trigger : TRUE,
13095 				convert_urls : TRUE,
13096 				relative_urls : TRUE,
13097 				remove_script_host : TRUE,
13098 				table_inline_editing : false,
13099 				object_resizing : TRUE,
13100 				accessibility_focus : TRUE,
13101 				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
13102 				visual : TRUE,
13103 				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
13104 				font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
13105 				apply_source_formatting : TRUE,
13106 				directionality : 'ltr',
13107 				forced_root_block : 'p',
13108 				hidden_input : TRUE,
13109 				padd_empty_editor : TRUE,
13110 				render_ui : TRUE,
13111 				indentation : '30px',
13112 				fix_table_elements : TRUE,
13113 				inline_styles : TRUE,
13114 				convert_fonts_to_spans : TRUE,
13115 				indent : 'simple',
13116 				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13117 				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13118 				validate : TRUE,
13119 				entity_encoding : 'named',
13120 				url_converter : self.convertURL,
13121 				url_converter_scope : self,
13122 				ie7_compat : TRUE
13123 			}, settings);
13124 
13125 			self.id = self.editorId = id;
13126 
13127 			self.isNotDirty = false;
13128 
13129 			self.plugins = {};
13130 
13131 			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
13132 				base_uri : tinyMCE.baseURI
13133 			});
13134 
13135 			self.baseURI = tinymce.baseURI;
13136 
13137 			self.contentCSS = [];
13138 
13139 			self.contentStyles = [];
13140 
13141 			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
13142 			self.setupEvents();
13143 
13144 			// Internal command handler objects
13145 			self.execCommands = {};
13146 			self.queryStateCommands = {};
13147 			self.queryValueCommands = {};
13148 
13149 			// Call setup
13150 			self.execCallback('setup', self);
13151 		},
13152 
13153 		render : function(nst) {
13154 			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
13155 
13156 			// Page is not loaded yet, wait for it
13157 			if (!Event.domLoaded) {
13158 				Event.add(window, 'ready', function() {
13159 					t.render();
13160 				});
13161 				return;
13162 			}
13163 
13164 			tinyMCE.settings = s;
13165 
13166 			// Element not found, then skip initialization
13167 			if (!t.getElement())
13168 				return;
13169 
13170 			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
13171 			// here since the browser says it has contentEditable support but there is no visible caret.
13172 			if (tinymce.isIDevice && !tinymce.isIOS5)
13173 				return;
13174 
13175 			// Add hidden input for non input elements inside form elements
13176 			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
13177 				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
13178 
13179 			// Hide target element early to prevent content flashing
13180 			if (!s.content_editable) {
13181 				t.orgVisibility = t.getElement().style.visibility;
13182 				t.getElement().style.visibility = 'hidden';
13183 			}
13184 
13185 			if (tinymce.WindowManager)
13186 				t.windowManager = new tinymce.WindowManager(t);
13187 
13188 			if (s.encoding == 'xml') {
13189 				t.onGetContent.add(function(ed, o) {
13190 					if (o.save)
13191 						o.content = DOM.encode(o.content);
13192 				});
13193 			}
13194 
13195 			if (s.add_form_submit_trigger) {
13196 				t.onSubmit.addToTop(function() {
13197 					if (t.initialized) {
13198 						t.save();
13199 						t.isNotDirty = 1;
13200 					}
13201 				});
13202 			}
13203 
13204 			if (s.add_unload_trigger) {
13205 				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
13206 					if (t.initialized && !t.destroyed && !t.isHidden())
13207 						t.save({format : 'raw', no_events : true});
13208 				});
13209 			}
13210 
13211 			tinymce.addUnload(t.destroy, t);
13212 
13213 			if (s.submit_patch) {
13214 				t.onBeforeRenderUI.add(function() {
13215 					var n = t.getElement().form;
13216 
13217 					if (!n)
13218 						return;
13219 
13220 					// Already patched
13221 					if (n._mceOldSubmit)
13222 						return;
13223 
13224 					// Check page uses id="submit" or name="submit" for it's submit button
13225 					if (!n.submit.nodeType && !n.submit.length) {
13226 						t.formElement = n;
13227 						n._mceOldSubmit = n.submit;
13228 						n.submit = function() {
13229 							// Save all instances
13230 							tinymce.triggerSave();
13231 							t.isNotDirty = 1;
13232 
13233 							return t.formElement._mceOldSubmit(t.formElement);
13234 						};
13235 					}
13236 
13237 					n = null;
13238 				});
13239 			}
13240 
13241 			// Load scripts
13242 			function loadScripts() {
13243 				if (s.language && s.language_load !== false)
13244 					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
13245 
13246 				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
13247 					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
13248 
13249 				each(explode(s.plugins), function(p) {
13250 					if (p &&!PluginManager.urls[p]) {
13251 						if (p.charAt(0) == '-') {
13252 							p = p.substr(1, p.length);
13253 							var dependencies = PluginManager.dependencies(p);
13254 							each(dependencies, function(dep) {
13255 								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
13256 								dep = PluginManager.createUrl(defaultSettings, dep);
13257 								PluginManager.load(dep.resource, dep);
13258 							});
13259 						} else {
13260 							// Skip safari plugin, since it is removed as of 3.3b1
13261 							if (p == 'safari') {
13262 								return;
13263 							}
13264 							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
13265 						}
13266 					}
13267 				});
13268 
13269 				// Init when que is loaded
13270 				sl.loadQueue(function() {
13271 					if (!t.removed)
13272 						t.init();
13273 				});
13274 			};
13275 
13276 			loadScripts();
13277 		},
13278 
13279 		init : function() {
13280 			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
13281 
13282 			tinymce.add(t);
13283 
13284 			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
13285 
13286 			if (s.theme) {
13287 				if (typeof s.theme != "function") {
13288 					s.theme = s.theme.replace(/-/, '');
13289 					o = ThemeManager.get(s.theme);
13290 					t.theme = new o();
13291 
13292 					if (t.theme.init)
13293 						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
13294 				} else {
13295 					t.theme = s.theme;
13296 				}
13297 			}
13298 
13299 			function initPlugin(p) {
13300 				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
13301 				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
13302 					each(PluginManager.dependencies(p), function(dep){
13303 						initPlugin(dep);
13304 					});
13305 					po = new c(t, u);
13306 
13307 					t.plugins[p] = po;
13308 
13309 					if (po.init) {
13310 						po.init(t, u);
13311 						initializedPlugins.push(p);
13312 					}
13313 				}
13314 			}
13315 			
13316 			// Create all plugins
13317 			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
13318 
13319 			// Setup popup CSS path(s)
13320 			if (s.popup_css !== false) {
13321 				if (s.popup_css)
13322 					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
13323 				else
13324 					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
13325 			}
13326 
13327 			if (s.popup_css_add)
13328 				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
13329 
13330 			t.controlManager = new tinymce.ControlManager(t);
13331 
13332 			// Enables users to override the control factory
13333 			t.onBeforeRenderUI.dispatch(t, t.controlManager);
13334 
13335 			// Measure box
13336 			if (s.render_ui && t.theme) {
13337 				t.orgDisplay = e.style.display;
13338 
13339 				if (typeof s.theme != "function") {
13340 					w = s.width || e.style.width || e.offsetWidth;
13341 					h = s.height || e.style.height || e.offsetHeight;
13342 					mh = s.min_height || 100;
13343 					re = /^[0-9\.]+(|px)$/i;
13344 
13345 					if (re.test('' + w))
13346 						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
13347 
13348 					if (re.test('' + h))
13349 						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
13350 
13351 					// Render UI
13352 					o = t.theme.renderUI({
13353 						targetNode : e,
13354 						width : w,
13355 						height : h,
13356 						deltaWidth : s.delta_width,
13357 						deltaHeight : s.delta_height
13358 					});
13359 
13360 					// Resize editor
13361 					DOM.setStyles(o.sizeContainer || o.editorContainer, {
13362 						width : w,
13363 						height : h
13364 					});
13365 
13366 					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
13367 					if (h < mh)
13368 						h = mh;
13369 				} else {
13370 					o = s.theme(t, e);
13371 
13372 					// Convert element type to id:s
13373 					if (o.editorContainer.nodeType) {
13374 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
13375 					}
13376 
13377 					// Convert element type to id:s
13378 					if (o.iframeContainer.nodeType) {
13379 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
13380 					}
13381 
13382 					// Use specified iframe height or the targets offsetHeight
13383 					h = o.iframeHeight || e.offsetHeight;
13384 
13385 					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
13386 					if (isIE) {
13387 						t.onInit.add(function(ed) {
13388 							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
13389 								ed.lastIERng = ed.selection.getRng();
13390 							});
13391 						});
13392 					}
13393 				}
13394 
13395 				t.editorContainer = o.editorContainer;
13396 			}
13397 
13398 			// Load specified content CSS last
13399 			if (s.content_css) {
13400 				each(explode(s.content_css), function(u) {
13401 					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
13402 				});
13403 			}
13404 
13405 			// Content editable mode ends here
13406 			if (s.content_editable) {
13407 				e = n = o = null; // Fix IE leak
13408 				return t.initContentBody();
13409 			}
13410 
13411 			// User specified a document.domain value
13412 			if (document.domain && location.hostname != document.domain)
13413 				tinymce.relaxedDomain = document.domain;
13414 
13415 			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
13416 
13417 			// We only need to override paths if we have to
13418 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
13419 			if (s.document_base_url != tinymce.documentBaseURL)
13420 				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
13421 
13422 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
13423 			if (s.ie7_compat)
13424 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
13425 			else
13426 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
13427 
13428 			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
13429 
13430 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
13431 			for (i = 0; i < t.contentCSS.length; i++) {
13432 				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
13433 			}
13434 
13435 			t.contentCSS = [];
13436 
13437 			bi = s.body_id || 'tinymce';
13438 			if (bi.indexOf('=') != -1) {
13439 				bi = t.getParam('body_id', '', 'hash');
13440 				bi = bi[t.id] || bi;
13441 			}
13442 
13443 			bc = s.body_class || '';
13444 			if (bc.indexOf('=') != -1) {
13445 				bc = t.getParam('body_class', '', 'hash');
13446 				bc = bc[t.id] || '';
13447 			}
13448 
13449 			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
13450 
13451 			// Domain relaxing enabled, then set document domain
13452 			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
13453 				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
13454 				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
13455 			}
13456 
13457 			// Create iframe
13458 			// TODO: ACC add the appropriate description on this.
13459 			n = DOM.add(o.iframeContainer, 'iframe', { 
13460 				id : t.id + "_ifr",
13461 				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
13462 				frameBorder : '0',
13463 				allowTransparency : "true",
13464 				title : s.aria_label,
13465 				style : {
13466 					width : '100%',
13467 					height : h,
13468 					display : 'block' // Important for Gecko to render the iframe correctly
13469 				}
13470 			});
13471 
13472 			t.contentAreaContainer = o.iframeContainer;
13473 
13474 			if (o.editorContainer) {
13475 				DOM.get(o.editorContainer).style.display = t.orgDisplay;
13476 			}
13477 
13478 			// Restore visibility on target element
13479 			e.style.visibility = t.orgVisibility;
13480 
13481 			DOM.get(t.id).style.display = 'none';
13482 			DOM.setAttrib(t.id, 'aria-hidden', true);
13483 
13484 			if (!tinymce.relaxedDomain || !u)
13485 				t.initContentBody();
13486 
13487 			e = n = o = null; // Cleanup
13488 		},
13489 
13490 		initContentBody : function() {
13491 			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
13492 
13493 			// Setup iframe body
13494 			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
13495 				doc.open();
13496 				doc.write(self.iframeHTML);
13497 				doc.close();
13498 
13499 				if (tinymce.relaxedDomain)
13500 					doc.domain = tinymce.relaxedDomain;
13501 			}
13502 
13503 			if (settings.content_editable) {
13504 				DOM.addClass(targetElm, 'mceContentBody');
13505 				self.contentDocument = doc = settings.content_document || document;
13506 				self.contentWindow = settings.content_window || window;
13507 				self.bodyElement = targetElm;
13508 
13509 				// Prevent leak in IE
13510 				settings.content_document = settings.content_window = null;
13511 			}
13512 
13513 			// It will not steal focus while setting contentEditable
13514 			body = self.getBody();
13515 			body.disabled = true;
13516 
13517 			if (!settings.readonly)
13518 				body.contentEditable = self.getParam('content_editable_state', true);
13519 
13520 			body.disabled = false;
13521 
13522 			self.schema = new tinymce.html.Schema(settings);
13523 
13524 			self.dom = new tinymce.dom.DOMUtils(doc, {
13525 				keep_values : true,
13526 				url_converter : self.convertURL,
13527 				url_converter_scope : self,
13528 				hex_colors : settings.force_hex_style_colors,
13529 				class_filter : settings.class_filter,
13530 				update_styles : true,
13531 				root_element : settings.content_editable ? self.id : null,
13532 				schema : self.schema
13533 			});
13534 
13535 			self.parser = new tinymce.html.DomParser(settings, self.schema);
13536 
13537 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
13538 			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
13539 				var i = nodes.length, node, dom = self.dom, value, internalName;
13540 
13541 				while (i--) {
13542 					node = nodes[i];
13543 					value = node.attr(name);
13544 					internalName = 'data-mce-' + name;
13545 
13546 					// Add internal attribute if we need to we don't on a refresh of the document
13547 					if (!node.attributes.map[internalName]) {	
13548 						if (name === "style")
13549 							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
13550 						else
13551 							node.attr(internalName, self.convertURL(value, name, node.name));
13552 					}
13553 				}
13554 			});
13555 
13556 			// Keep scripts from executing
13557 			self.parser.addNodeFilter('script', function(nodes, name) {
13558 				var i = nodes.length, node;
13559 
13560 				while (i--) {
13561 					node = nodes[i];
13562 					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
13563 				}
13564 			});
13565 
13566 			self.parser.addNodeFilter('#cdata', function(nodes, name) {
13567 				var i = nodes.length, node;
13568 
13569 				while (i--) {
13570 					node = nodes[i];
13571 					node.type = 8;
13572 					node.name = '#comment';
13573 					node.value = '[CDATA[' + node.value + ']]';
13574 				}
13575 			});
13576 
13577 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
13578 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
13579 
13580 				while (i--) {
13581 					node = nodes[i];
13582 
13583 					if (node.isEmpty(nonEmptyElements))
13584 						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
13585 				}
13586 			});
13587 
13588 			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
13589 
13590 			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
13591 
13592 			self.formatter = new tinymce.Formatter(self);
13593 
13594 			self.undoManager = new tinymce.UndoManager(self);
13595 
13596 			self.forceBlocks = new tinymce.ForceBlocks(self);
13597 			self.enterKey = new tinymce.EnterKey(self);
13598 			self.editorCommands = new tinymce.EditorCommands(self);
13599 
13600 			self.onExecCommand.add(function(editor, command) {
13601 				// Don't refresh the select lists until caret move
13602 				if (!/^(FontName|FontSize)$/.test(command))
13603 					self.nodeChanged();
13604 			});
13605 
13606 			// Pass through
13607 			self.serializer.onPreProcess.add(function(se, o) {
13608 				return self.onPreProcess.dispatch(self, o, se);
13609 			});
13610 
13611 			self.serializer.onPostProcess.add(function(se, o) {
13612 				return self.onPostProcess.dispatch(self, o, se);
13613 			});
13614 
13615 			self.onPreInit.dispatch(self);
13616 
13617 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
13618 				doc.body.spellcheck = false;
13619 
13620 			if (!settings.readonly) {
13621 				self.bindNativeEvents();
13622 			}
13623 
13624 			self.controlManager.onPostRender.dispatch(self, self.controlManager);
13625 			self.onPostRender.dispatch(self);
13626 
13627 			self.quirks = tinymce.util.Quirks(self);
13628 
13629 			if (settings.directionality)
13630 				body.dir = settings.directionality;
13631 
13632 			if (settings.nowrap)
13633 				body.style.whiteSpace = "nowrap";
13634 
13635 			if (settings.protect) {
13636 				self.onBeforeSetContent.add(function(ed, o) {
13637 					each(settings.protect, function(pattern) {
13638 						o.content = o.content.replace(pattern, function(str) {
13639 							return '<!--mce:protected ' + escape(str) + '-->';
13640 						});
13641 					});
13642 				});
13643 			}
13644 
13645 			// Add visual aids when new contents is added
13646 			self.onSetContent.add(function() {
13647 				self.addVisual(self.getBody());
13648 			});
13649 
13650 			// Remove empty contents
13651 			if (settings.padd_empty_editor) {
13652 				self.onPostProcess.add(function(ed, o) {
13653 					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
13654 				});
13655 			}
13656 
13657 			self.load({initial : true, format : 'html'});
13658 			self.startContent = self.getContent({format : 'raw'});
13659 
13660 			self.initialized = true;
13661 
13662 			self.onInit.dispatch(self);
13663 			self.execCallback('setupcontent_callback', self.id, body, doc);
13664 			self.execCallback('init_instance_callback', self);
13665 			self.focus(true);
13666 			self.nodeChanged({initial : true});
13667 
13668 			// Add editor specific CSS styles
13669 			if (self.contentStyles.length > 0) {
13670 				contentCssText = '';
13671 
13672 				each(self.contentStyles, function(style) {
13673 					contentCssText += style + "\r\n";
13674 				});
13675 
13676 				self.dom.addStyle(contentCssText);
13677 			}
13678 
13679 			// Load specified content CSS last
13680 			each(self.contentCSS, function(url) {
13681 				self.dom.loadCSS(url);
13682 			});
13683 
13684 			// Handle auto focus
13685 			if (settings.auto_focus) {
13686 				setTimeout(function () {
13687 					var ed = tinymce.get(settings.auto_focus);
13688 
13689 					ed.selection.select(ed.getBody(), 1);
13690 					ed.selection.collapse(1);
13691 					ed.getBody().focus();
13692 					ed.getWin().focus();
13693 				}, 100);
13694 			}
13695 
13696 			// Clean up references for IE
13697 			targetElm = doc = body = null;
13698 		},
13699 
13700 		focus : function(skip_focus) {
13701 			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
13702 
13703 			if (!skip_focus) {
13704 				if (self.lastIERng) {
13705 					selection.setRng(self.lastIERng);
13706 				}
13707 
13708 				// Get selected control element
13709 				ieRng = selection.getRng();
13710 				if (ieRng.item) {
13711 					controlElm = ieRng.item(0);
13712 				}
13713 
13714 				self._refreshContentEditable();
13715 
13716 				// Focus the window iframe
13717 				if (!contentEditable) {
13718 					self.getWin().focus();
13719 				}
13720 
13721 				// Focus the body as well since it's contentEditable
13722 				if (tinymce.isGecko || contentEditable) {
13723 					body = self.getBody();
13724 
13725 					// Check for setActive since it doesn't scroll to the element
13726 					if (body.setActive) {
13727 						body.setActive();
13728 					} else {
13729 						body.focus();
13730 					}
13731 
13732 					if (contentEditable) {
13733 						selection.normalize();
13734 					}
13735 				}
13736 
13737 				// Restore selected control element
13738 				// This is needed when for example an image is selected within a
13739 				// layer a call to focus will then remove the control selection
13740 				if (controlElm && controlElm.ownerDocument == doc) {
13741 					ieRng = doc.body.createControlRange();
13742 					ieRng.addElement(controlElm);
13743 					ieRng.select();
13744 				}
13745 			}
13746 
13747 			if (tinymce.activeEditor != self) {
13748 				if ((oed = tinymce.activeEditor) != null)
13749 					oed.onDeactivate.dispatch(oed, self);
13750 
13751 				self.onActivate.dispatch(self, oed);
13752 			}
13753 
13754 			tinymce._setActive(self);
13755 		},
13756 
13757 		execCallback : function(n) {
13758 			var t = this, f = t.settings[n], s;
13759 
13760 			if (!f)
13761 				return;
13762 
13763 			// Look through lookup
13764 			if (t.callbackLookup && (s = t.callbackLookup[n])) {
13765 				f = s.func;
13766 				s = s.scope;
13767 			}
13768 
13769 			if (is(f, 'string')) {
13770 				s = f.replace(/\.\w+$/, '');
13771 				s = s ? tinymce.resolve(s) : 0;
13772 				f = tinymce.resolve(f);
13773 				t.callbackLookup = t.callbackLookup || {};
13774 				t.callbackLookup[n] = {func : f, scope : s};
13775 			}
13776 
13777 			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
13778 		},
13779 
13780 		translate : function(s) {
13781 			var c = this.settings.language || 'en', i18n = tinymce.i18n;
13782 
13783 			if (!s)
13784 				return '';
13785 
13786 			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
13787 				return i18n[c + '.' + b] || '{#' + b + '}';
13788 			});
13789 		},
13790 
13791 		getLang : function(n, dv) {
13792 			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
13793 		},
13794 
13795 		getParam : function(n, dv, ty) {
13796 			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
13797 
13798 			if (ty === 'hash') {
13799 				o = {};
13800 
13801 				if (is(v, 'string')) {
13802 					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
13803 						v = v.split('=');
13804 
13805 						if (v.length > 1)
13806 							o[tr(v[0])] = tr(v[1]);
13807 						else
13808 							o[tr(v[0])] = tr(v);
13809 					});
13810 				} else
13811 					o = v;
13812 
13813 				return o;
13814 			}
13815 
13816 			return v;
13817 		},
13818 
13819 		nodeChanged : function(o) {
13820 			var self = this, selection = self.selection, node;
13821 
13822 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
13823 			if (self.initialized) {
13824 				o = o || {};
13825 
13826 				// Get start node
13827 				node = selection.getStart() || self.getBody();
13828 				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
13829 
13830 				// Get parents and add them to object
13831 				o.parents = [];
13832 				self.dom.getParent(node, function(node) {
13833 					if (node.nodeName == 'BODY')
13834 						return true;
13835 
13836 					o.parents.push(node);
13837 				});
13838 
13839 				self.onNodeChange.dispatch(
13840 					self,
13841 					o ? o.controlManager || self.controlManager : self.controlManager,
13842 					node,
13843 					selection.isCollapsed(),
13844 					o
13845 				);
13846 			}
13847 		},
13848 
13849 		addButton : function(name, settings) {
13850 			var self = this;
13851 
13852 			self.buttons = self.buttons || {};
13853 			self.buttons[name] = settings;
13854 		},
13855 
13856 		addCommand : function(name, callback, scope) {
13857 			this.execCommands[name] = {func : callback, scope : scope || this};
13858 		},
13859 
13860 		addQueryStateHandler : function(name, callback, scope) {
13861 			this.queryStateCommands[name] = {func : callback, scope : scope || this};
13862 		},
13863 
13864 		addQueryValueHandler : function(name, callback, scope) {
13865 			this.queryValueCommands[name] = {func : callback, scope : scope || this};
13866 		},
13867 
13868 		addShortcut : function(pa, desc, cmd_func, sc) {
13869 			var t = this, c;
13870 
13871 			if (t.settings.custom_shortcuts === false)
13872 				return false;
13873 
13874 			t.shortcuts = t.shortcuts || {};
13875 
13876 			if (is(cmd_func, 'string')) {
13877 				c = cmd_func;
13878 
13879 				cmd_func = function() {
13880 					t.execCommand(c, false, null);
13881 				};
13882 			}
13883 
13884 			if (is(cmd_func, 'object')) {
13885 				c = cmd_func;
13886 
13887 				cmd_func = function() {
13888 					t.execCommand(c[0], c[1], c[2]);
13889 				};
13890 			}
13891 
13892 			each(explode(pa), function(pa) {
13893 				var o = {
13894 					func : cmd_func,
13895 					scope : sc || this,
13896 					desc : t.translate(desc),
13897 					alt : false,
13898 					ctrl : false,
13899 					shift : false
13900 				};
13901 
13902 				each(explode(pa, '+'), function(v) {
13903 					switch (v) {
13904 						case 'alt':
13905 						case 'ctrl':
13906 						case 'shift':
13907 							o[v] = true;
13908 							break;
13909 
13910 						default:
13911 							o.charCode = v.charCodeAt(0);
13912 							o.keyCode = v.toUpperCase().charCodeAt(0);
13913 					}
13914 				});
13915 
13916 				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
13917 			});
13918 
13919 			return true;
13920 		},
13921 
13922 		execCommand : function(cmd, ui, val, a) {
13923 			var t = this, s = 0, o, st;
13924 
13925 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
13926 				t.focus();
13927 
13928 			a = extend({}, a);
13929 			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
13930 			if (a.terminate)
13931 				return false;
13932 
13933 			// Command callback
13934 			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
13935 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13936 				return true;
13937 			}
13938 
13939 			// Registred commands
13940 			if (o = t.execCommands[cmd]) {
13941 				st = o.func.call(o.scope, ui, val);
13942 
13943 				// Fall through on true
13944 				if (st !== true) {
13945 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13946 					return st;
13947 				}
13948 			}
13949 
13950 			// Plugin commands
13951 			each(t.plugins, function(p) {
13952 				if (p.execCommand && p.execCommand(cmd, ui, val)) {
13953 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13954 					s = 1;
13955 					return false;
13956 				}
13957 			});
13958 
13959 			if (s)
13960 				return true;
13961 
13962 			// Theme commands
13963 			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
13964 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13965 				return true;
13966 			}
13967 
13968 			// Editor commands
13969 			if (t.editorCommands.execCommand(cmd, ui, val)) {
13970 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13971 				return true;
13972 			}
13973 
13974 			// Browser commands
13975 			t.getDoc().execCommand(cmd, ui, val);
13976 			t.onExecCommand.dispatch(t, cmd, ui, val, a);
13977 		},
13978 
13979 		queryCommandState : function(cmd) {
13980 			var t = this, o, s;
13981 
13982 			// Is hidden then return undefined
13983 			if (t._isHidden())
13984 				return;
13985 
13986 			// Registred commands
13987 			if (o = t.queryStateCommands[cmd]) {
13988 				s = o.func.call(o.scope);
13989 
13990 				// Fall though on true
13991 				if (s !== true)
13992 					return s;
13993 			}
13994 
13995 			// Registred commands
13996 			o = t.editorCommands.queryCommandState(cmd);
13997 			if (o !== -1)
13998 				return o;
13999 
14000 			// Browser commands
14001 			try {
14002 				return this.getDoc().queryCommandState(cmd);
14003 			} catch (ex) {
14004 				// Fails sometimes see bug: 1896577
14005 			}
14006 		},
14007 
14008 		queryCommandValue : function(c) {
14009 			var t = this, o, s;
14010 
14011 			// Is hidden then return undefined
14012 			if (t._isHidden())
14013 				return;
14014 
14015 			// Registred commands
14016 			if (o = t.queryValueCommands[c]) {
14017 				s = o.func.call(o.scope);
14018 
14019 				// Fall though on true
14020 				if (s !== true)
14021 					return s;
14022 			}
14023 
14024 			// Registred commands
14025 			o = t.editorCommands.queryCommandValue(c);
14026 			if (is(o))
14027 				return o;
14028 
14029 			// Browser commands
14030 			try {
14031 				return this.getDoc().queryCommandValue(c);
14032 			} catch (ex) {
14033 				// Fails sometimes see bug: 1896577
14034 			}
14035 		},
14036 
14037 		show : function() {
14038 			var self = this;
14039 
14040 			DOM.show(self.getContainer());
14041 			DOM.hide(self.id);
14042 			self.load();
14043 		},
14044 
14045 		hide : function() {
14046 			var self = this, doc = self.getDoc();
14047 
14048 			// Fixed bug where IE has a blinking cursor left from the editor
14049 			if (isIE && doc)
14050 				doc.execCommand('SelectAll');
14051 
14052 			// We must save before we hide so Safari doesn't crash
14053 			self.save();
14054 			DOM.hide(self.getContainer());
14055 			DOM.setStyle(self.id, 'display', self.orgDisplay);
14056 		},
14057 
14058 		isHidden : function() {
14059 			return !DOM.isHidden(this.id);
14060 		},
14061 
14062 		setProgressState : function(b, ti, o) {
14063 			this.onSetProgressState.dispatch(this, b, ti, o);
14064 
14065 			return b;
14066 		},
14067 
14068 		load : function(o) {
14069 			var t = this, e = t.getElement(), h;
14070 
14071 			if (e) {
14072 				o = o || {};
14073 				o.load = true;
14074 
14075 				// Double encode existing entities in the value
14076 				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
14077 				o.element = e;
14078 
14079 				if (!o.no_events)
14080 					t.onLoadContent.dispatch(t, o);
14081 
14082 				o.element = e = null;
14083 
14084 				return h;
14085 			}
14086 		},
14087 
14088 		save : function(o) {
14089 			var t = this, e = t.getElement(), h, f;
14090 
14091 			if (!e || !t.initialized)
14092 				return;
14093 
14094 			o = o || {};
14095 			o.save = true;
14096 
14097 			o.element = e;
14098 			h = o.content = t.getContent(o);
14099 
14100 			if (!o.no_events)
14101 				t.onSaveContent.dispatch(t, o);
14102 
14103 			h = o.content;
14104 
14105 			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
14106 				e.innerHTML = h;
14107 
14108 				// Update hidden form element
14109 				if (f = DOM.getParent(t.id, 'form')) {
14110 					each(f.elements, function(e) {
14111 						if (e.name == t.id) {
14112 							e.value = h;
14113 							return false;
14114 						}
14115 					});
14116 				}
14117 			} else
14118 				e.value = h;
14119 
14120 			o.element = e = null;
14121 
14122 			return h;
14123 		},
14124 
14125 		setContent : function(content, args) {
14126 			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
14127 
14128 			// Setup args object
14129 			args = args || {};
14130 			args.format = args.format || 'html';
14131 			args.set = true;
14132 			args.content = content;
14133 
14134 			// Do preprocessing
14135 			if (!args.no_events)
14136 				self.onBeforeSetContent.dispatch(self, args);
14137 
14138 			content = args.content;
14139 
14140 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
14141 			// It will also be impossible to place the caret in the editor unless there is a BR element present
14142 			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
14143 				forcedRootBlockName = self.settings.forced_root_block;
14144 				if (forcedRootBlockName)
14145 					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
14146 				else
14147 					content = '<br data-mce-bogus="1">';
14148 
14149 				body.innerHTML = content;
14150 				self.selection.select(body, true);
14151 				self.selection.collapse(true);
14152 				return;
14153 			}
14154 
14155 			// Parse and serialize the html
14156 			if (args.format !== 'raw') {
14157 				content = new tinymce.html.Serializer({}, self.schema).serialize(
14158 					self.parser.parse(content)
14159 				);
14160 			}
14161 
14162 			// Set the new cleaned contents to the editor
14163 			args.content = tinymce.trim(content);
14164 			self.dom.setHTML(body, args.content);
14165 
14166 			// Do post processing
14167 			if (!args.no_events)
14168 				self.onSetContent.dispatch(self, args);
14169 
14170 			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
14171 			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
14172 				self.selection.normalize();
14173 			}
14174 
14175 			return args.content;
14176 		},
14177 
14178 		getContent : function(args) {
14179 			var self = this, content;
14180 
14181 			// Setup args object
14182 			args = args || {};
14183 			args.format = args.format || 'html';
14184 			args.get = true;
14185 			args.getInner = true;
14186 
14187 			// Do preprocessing
14188 			if (!args.no_events)
14189 				self.onBeforeGetContent.dispatch(self, args);
14190 
14191 			// Get raw contents or by default the cleaned contents
14192 			if (args.format == 'raw')
14193 				content = self.getBody().innerHTML;
14194 			else
14195 				content = self.serializer.serialize(self.getBody(), args);
14196 
14197 			args.content = tinymce.trim(content);
14198 
14199 			// Do post processing
14200 			if (!args.no_events)
14201 				self.onGetContent.dispatch(self, args);
14202 
14203 			return args.content;
14204 		},
14205 
14206 		isDirty : function() {
14207 			var self = this;
14208 
14209 			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
14210 		},
14211 
14212 		getContainer : function() {
14213 			var self = this;
14214 
14215 			if (!self.container)
14216 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
14217 
14218 			return self.container;
14219 		},
14220 
14221 		getContentAreaContainer : function() {
14222 			return this.contentAreaContainer;
14223 		},
14224 
14225 		getElement : function() {
14226 			return DOM.get(this.settings.content_element || this.id);
14227 		},
14228 
14229 		getWin : function() {
14230 			var self = this, elm;
14231 
14232 			if (!self.contentWindow) {
14233 				elm = DOM.get(self.id + "_ifr");
14234 
14235 				if (elm)
14236 					self.contentWindow = elm.contentWindow;
14237 			}
14238 
14239 			return self.contentWindow;
14240 		},
14241 
14242 		getDoc : function() {
14243 			var self = this, win;
14244 
14245 			if (!self.contentDocument) {
14246 				win = self.getWin();
14247 
14248 				if (win)
14249 					self.contentDocument = win.document;
14250 			}
14251 
14252 			return self.contentDocument;
14253 		},
14254 
14255 		getBody : function() {
14256 			return this.bodyElement || this.getDoc().body;
14257 		},
14258 
14259 		convertURL : function(url, name, elm) {
14260 			var self = this, settings = self.settings;
14261 
14262 			// Use callback instead
14263 			if (settings.urlconverter_callback)
14264 				return self.execCallback('urlconverter_callback', url, elm, true, name);
14265 
14266 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
14267 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
14268 				return url;
14269 
14270 			// Convert to relative
14271 			if (settings.relative_urls)
14272 				return self.documentBaseURI.toRelative(url);
14273 
14274 			// Convert to absolute
14275 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
14276 
14277 			return url;
14278 		},
14279 
14280 		addVisual : function(elm) {
14281 			var self = this, settings = self.settings, dom = self.dom, cls;
14282 
14283 			elm = elm || self.getBody();
14284 
14285 			if (!is(self.hasVisual))
14286 				self.hasVisual = settings.visual;
14287 
14288 			each(dom.select('table,a', elm), function(elm) {
14289 				var value;
14290 
14291 				switch (elm.nodeName) {
14292 					case 'TABLE':
14293 						cls = settings.visual_table_class || 'mceItemTable';
14294 						value = dom.getAttrib(elm, 'border');
14295 
14296 						if (!value || value == '0') {
14297 							if (self.hasVisual)
14298 								dom.addClass(elm, cls);
14299 							else
14300 								dom.removeClass(elm, cls);
14301 						}
14302 
14303 						return;
14304 
14305 					case 'A':
14306 						if (!dom.getAttrib(elm, 'href', false)) {
14307 							value = dom.getAttrib(elm, 'name') || elm.id;
14308 							cls = 'mceItemAnchor';
14309 
14310 							if (value) {
14311 								if (self.hasVisual)
14312 									dom.addClass(elm, cls);
14313 								else
14314 									dom.removeClass(elm, cls);
14315 							}
14316 						}
14317 
14318 						return;
14319 				}
14320 			});
14321 
14322 			self.onVisualAid.dispatch(self, elm, self.hasVisual);
14323 		},
14324 
14325 		remove : function() {
14326 			var self = this, elm = self.getContainer();
14327 
14328 			if (!self.removed) {
14329 				self.removed = 1; // Cancels post remove event execution
14330 				self.hide();
14331 
14332 				// Don't clear the window or document if content editable
14333 				// is enabled since other instances might still be present
14334 				if (!self.settings.content_editable) {
14335 					Event.unbind(self.getWin());
14336 					Event.unbind(self.getDoc());
14337 				}
14338 
14339 				Event.unbind(self.getBody());
14340 				Event.clear(elm);
14341 
14342 				self.execCallback('remove_instance_callback', self);
14343 				self.onRemove.dispatch(self);
14344 
14345 				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
14346 				self.onExecCommand.listeners = [];
14347 
14348 				tinymce.remove(self);
14349 				DOM.remove(elm);
14350 			}
14351 		},
14352 
14353 		destroy : function(s) {
14354 			var t = this;
14355 
14356 			// One time is enough
14357 			if (t.destroyed)
14358 				return;
14359 
14360 			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
14361 			if (isGecko) {
14362 				Event.unbind(t.getDoc());
14363 				Event.unbind(t.getWin());
14364 				Event.unbind(t.getBody());
14365 			}
14366 
14367 			if (!s) {
14368 				tinymce.removeUnload(t.destroy);
14369 				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
14370 
14371 				// Manual destroy
14372 				if (t.theme && t.theme.destroy)
14373 					t.theme.destroy();
14374 
14375 				// Destroy controls, selection and dom
14376 				t.controlManager.destroy();
14377 				t.selection.destroy();
14378 				t.dom.destroy();
14379 			}
14380 
14381 			if (t.formElement) {
14382 				t.formElement.submit = t.formElement._mceOldSubmit;
14383 				t.formElement._mceOldSubmit = null;
14384 			}
14385 
14386 			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
14387 
14388 			if (t.selection)
14389 				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
14390 
14391 			t.destroyed = 1;
14392 		},
14393 
14394 		// Internal functions
14395 
14396 		_refreshContentEditable : function() {
14397 			var self = this, body, parent;
14398 
14399 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
14400 			if (self._isHidden()) {
14401 				body = self.getBody();
14402 				parent = body.parentNode;
14403 
14404 				parent.removeChild(body);
14405 				parent.appendChild(body);
14406 
14407 				body.focus();
14408 			}
14409 		},
14410 
14411 		_isHidden : function() {
14412 			var s;
14413 
14414 			if (!isGecko)
14415 				return 0;
14416 
14417 			// Weird, wheres that cursor selection?
14418 			s = this.selection.getSel();
14419 			return (!s || !s.rangeCount || s.rangeCount === 0);
14420 		}
14421 	});
14422 })(tinymce);
14423 (function(tinymce) {
14424 	var each = tinymce.each;
14425 
14426 	tinymce.Editor.prototype.setupEvents = function() {
14427 		var self = this, settings = self.settings;
14428 
14429 		// Add events to the editor
14430 		each([
14431 			'onPreInit',
14432 
14433 			'onBeforeRenderUI',
14434 
14435 			'onPostRender',
14436 
14437 			'onLoad',
14438 
14439 			'onInit',
14440 
14441 			'onRemove',
14442 
14443 			'onActivate',
14444 
14445 			'onDeactivate',
14446 
14447 			'onClick',
14448 
14449 			'onEvent',
14450 
14451 			'onMouseUp',
14452 
14453 			'onMouseDown',
14454 
14455 			'onDblClick',
14456 
14457 			'onKeyDown',
14458 
14459 			'onKeyUp',
14460 
14461 			'onKeyPress',
14462 
14463 			'onContextMenu',
14464 
14465 			'onSubmit',
14466 
14467 			'onReset',
14468 
14469 			'onPaste',
14470 
14471 			'onPreProcess',
14472 
14473 			'onPostProcess',
14474 
14475 			'onBeforeSetContent',
14476 
14477 			'onBeforeGetContent',
14478 
14479 			'onSetContent',
14480 
14481 			'onGetContent',
14482 
14483 			'onLoadContent',
14484 
14485 			'onSaveContent',
14486 
14487 			'onNodeChange',
14488 
14489 			'onChange',
14490 
14491 			'onBeforeExecCommand',
14492 
14493 			'onExecCommand',
14494 
14495 			'onUndo',
14496 
14497 			'onRedo',
14498 
14499 			'onVisualAid',
14500 
14501 			'onSetProgressState',
14502 
14503 			'onSetAttrib'
14504 		], function(name) {
14505 			self[name] = new tinymce.util.Dispatcher(self);
14506 		});
14507 
14508 		// Handle legacy cleanup_callback option
14509 		if (settings.cleanup_callback) {
14510 			self.onBeforeSetContent.add(function(ed, o) {
14511 				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14512 			});
14513 
14514 			self.onPreProcess.add(function(ed, o) {
14515 				if (o.set)
14516 					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
14517 
14518 				if (o.get)
14519 					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
14520 			});
14521 
14522 			self.onPostProcess.add(function(ed, o) {
14523 				if (o.set)
14524 					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14525 
14526 				if (o.get)						
14527 					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
14528 			});
14529 		}
14530 
14531 		// Handle legacy save_callback option
14532 		if (settings.save_callback) {
14533 			self.onGetContent.add(function(ed, o) {
14534 				if (o.save)
14535 					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14536 			});
14537 		}
14538 
14539 		// Handle legacy handle_event_callback option
14540 		if (settings.handle_event_callback) {
14541 			self.onEvent.add(function(ed, e, o) {
14542 				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
14543 					e.preventDefault();
14544 					e.stopPropagation();
14545 				}
14546 			});
14547 		}
14548 
14549 		// Handle legacy handle_node_change_callback option
14550 		if (settings.handle_node_change_callback) {
14551 			self.onNodeChange.add(function(ed, cm, n) {
14552 				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
14553 			});
14554 		}
14555 
14556 		// Handle legacy save_callback option
14557 		if (settings.save_callback) {
14558 			self.onSaveContent.add(function(ed, o) {
14559 				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14560 
14561 				if (h)
14562 					o.content = h;
14563 			});
14564 		}
14565 
14566 		// Handle legacy onchange_callback option
14567 		if (settings.onchange_callback) {
14568 			self.onChange.add(function(ed, l) {
14569 				ed.execCallback('onchange_callback', ed, l);
14570 			});
14571 		}
14572 	};
14573 
14574 	tinymce.Editor.prototype.bindNativeEvents = function() {
14575 		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
14576 		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
14577 
14578 		nativeToDispatcherMap = {
14579 			mouseup : 'onMouseUp',
14580 			mousedown : 'onMouseDown',
14581 			click : 'onClick',
14582 			keyup : 'onKeyUp',
14583 			keydown : 'onKeyDown',
14584 			keypress : 'onKeyPress',
14585 			submit : 'onSubmit',
14586 			reset : 'onReset',
14587 			contextmenu : 'onContextMenu',
14588 			dblclick : 'onDblClick',
14589 			paste : 'onPaste' // Doesn't work in all browsers yet
14590 		};
14591 
14592 		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
14593 		function eventHandler(evt, args) {
14594 			var type = evt.type;
14595 
14596 			// Don't fire events when it's removed
14597 			if (self.removed)
14598 				return;
14599 
14600 			// Sends the native event out to a global dispatcher then to the specific event dispatcher
14601 			if (self.onEvent.dispatch(self, evt, args) !== false) {
14602 				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
14603 			}
14604 		};
14605 
14606 		// Opera doesn't support focus event for contentEditable elements so we need to fake it
14607 		function doOperaFocus(e) {
14608 			self.focus(true);
14609 		};
14610 
14611 		function nodeChanged(ed, e) {
14612 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
14613 			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
14614 				self.selection.normalize();
14615 			}
14616 
14617 			self.nodeChanged();
14618 		}
14619 
14620 		// Add DOM events
14621 		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
14622 			var root = settings.content_editable ? self.getBody() : self.getDoc();
14623 
14624 			switch (nativeName) {
14625 				case 'contextmenu':
14626 					dom.bind(root, nativeName, eventHandler);
14627 					break;
14628 
14629 				case 'paste':
14630 					dom.bind(self.getBody(), nativeName, eventHandler);
14631 					break;
14632 
14633 				case 'submit':
14634 				case 'reset':
14635 					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
14636 					break;
14637 
14638 				default:
14639 					dom.bind(root, nativeName, eventHandler);
14640 			}
14641 		});
14642 
14643 		// Set the editor as active when focused
14644 		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
14645 			self.focus(true);
14646 		});
14647 
14648 		if (settings.content_editable && tinymce.isOpera) {
14649 			dom.bind(self.getBody(), 'click', doOperaFocus);
14650 			dom.bind(self.getBody(), 'keydown', doOperaFocus);
14651 		}
14652 
14653 		// Add node change handler
14654 		self.onMouseUp.add(nodeChanged);
14655 
14656 		self.onKeyUp.add(function(ed, e) {
14657 			var keyCode = e.keyCode;
14658 
14659 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
14660 				nodeChanged(ed, e);
14661 		});
14662 
14663 		// Add reset handler
14664 		self.onReset.add(function() {
14665 			self.setContent(self.startContent, {format : 'raw'});
14666 		});
14667 
14668 		// Add shortcuts
14669 		function handleShortcut(e, execute) {
14670 			if (e.altKey || e.ctrlKey || e.metaKey) {
14671 				each(self.shortcuts, function(shortcut) {
14672 					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
14673 
14674 					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
14675 						return;
14676 
14677 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
14678 						e.preventDefault();
14679 
14680 						if (execute) {
14681 							shortcut.func.call(shortcut.scope);
14682 						}
14683 
14684 						return true;
14685 					}
14686 				});
14687 			}
14688 		};
14689 
14690 		self.onKeyUp.add(function(ed, e) {
14691 			handleShortcut(e);
14692 		});
14693 
14694 		self.onKeyPress.add(function(ed, e) {
14695 			handleShortcut(e);
14696 		});
14697 
14698 		self.onKeyDown.add(function(ed, e) {
14699 			handleShortcut(e, true);
14700 		});
14701 
14702 		if (tinymce.isOpera) {
14703 			self.onClick.add(function(ed, e) {
14704 				e.preventDefault();
14705 			});
14706 		}
14707 	};
14708 })(tinymce);
14709 (function(tinymce) {
14710 	// Added for compression purposes
14711 	var each = tinymce.each, undef, TRUE = true, FALSE = false;
14712 
14713 	tinymce.EditorCommands = function(editor) {
14714 		var dom = editor.dom,
14715 			selection = editor.selection,
14716 			commands = {state: {}, exec : {}, value : {}},
14717 			settings = editor.settings,
14718 			formatter = editor.formatter,
14719 			bookmark;
14720 
14721 		function execCommand(command, ui, value) {
14722 			var func;
14723 
14724 			command = command.toLowerCase();
14725 			if (func = commands.exec[command]) {
14726 				func(command, ui, value);
14727 				return TRUE;
14728 			}
14729 
14730 			return FALSE;
14731 		};
14732 
14733 		function queryCommandState(command) {
14734 			var func;
14735 
14736 			command = command.toLowerCase();
14737 			if (func = commands.state[command])
14738 				return func(command);
14739 
14740 			return -1;
14741 		};
14742 
14743 		function queryCommandValue(command) {
14744 			var func;
14745 
14746 			command = command.toLowerCase();
14747 			if (func = commands.value[command])
14748 				return func(command);
14749 
14750 			return FALSE;
14751 		};
14752 
14753 		function addCommands(command_list, type) {
14754 			type = type || 'exec';
14755 
14756 			each(command_list, function(callback, command) {
14757 				each(command.toLowerCase().split(','), function(command) {
14758 					commands[type][command] = callback;
14759 				});
14760 			});
14761 		};
14762 
14763 		// Expose public methods
14764 		tinymce.extend(this, {
14765 			execCommand : execCommand,
14766 			queryCommandState : queryCommandState,
14767 			queryCommandValue : queryCommandValue,
14768 			addCommands : addCommands
14769 		});
14770 
14771 		// Private methods
14772 
14773 		function execNativeCommand(command, ui, value) {
14774 			if (ui === undef)
14775 				ui = FALSE;
14776 
14777 			if (value === undef)
14778 				value = null;
14779 
14780 			return editor.getDoc().execCommand(command, ui, value);
14781 		};
14782 
14783 		function isFormatMatch(name) {
14784 			return formatter.match(name);
14785 		};
14786 
14787 		function toggleFormat(name, value) {
14788 			formatter.toggle(name, value ? {value : value} : undef);
14789 		};
14790 
14791 		function storeSelection(type) {
14792 			bookmark = selection.getBookmark(type);
14793 		};
14794 
14795 		function restoreSelection() {
14796 			selection.moveToBookmark(bookmark);
14797 		};
14798 
14799 		// Add execCommand overrides
14800 		addCommands({
14801 			// Ignore these, added for compatibility
14802 			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
14803 
14804 			// Add undo manager logic
14805 			'mceEndUndoLevel,mceAddUndoLevel' : function() {
14806 				editor.undoManager.add();
14807 			},
14808 
14809 			'Cut,Copy,Paste' : function(command) {
14810 				var doc = editor.getDoc(), failed;
14811 
14812 				// Try executing the native command
14813 				try {
14814 					execNativeCommand(command);
14815 				} catch (ex) {
14816 					// Command failed
14817 					failed = TRUE;
14818 				}
14819 
14820 				// Present alert message about clipboard access not being available
14821 				if (failed || !doc.queryCommandSupported(command)) {
14822 					if (tinymce.isGecko) {
14823 						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
14824 							if (state)
14825 								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
14826 						});
14827 					} else
14828 						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
14829 				}
14830 			},
14831 
14832 			// Override unlink command
14833 			unlink : function(command) {
14834 				if (selection.isCollapsed())
14835 					selection.select(selection.getNode());
14836 
14837 				execNativeCommand(command);
14838 				selection.collapse(FALSE);
14839 			},
14840 
14841 			// Override justify commands to use the text formatter engine
14842 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
14843 				var align = command.substring(7);
14844 
14845 				// Remove all other alignments first
14846 				each('left,center,right,full'.split(','), function(name) {
14847 					if (align != name)
14848 						formatter.remove('align' + name);
14849 				});
14850 
14851 				toggleFormat('align' + align);
14852 				execCommand('mceRepaint');
14853 			},
14854 
14855 			// Override list commands to fix WebKit bug
14856 			'InsertUnorderedList,InsertOrderedList' : function(command) {
14857 				var listElm, listParent;
14858 
14859 				execNativeCommand(command);
14860 
14861 				// WebKit produces lists within block elements so we need to split them
14862 				// we will replace the native list creation logic to custom logic later on
14863 				// TODO: Remove this when the list creation logic is removed
14864 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
14865 				if (listElm) {
14866 					listParent = listElm.parentNode;
14867 
14868 					// If list is within a text block then split that block
14869 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
14870 						storeSelection();
14871 						dom.split(listParent, listElm);
14872 						restoreSelection();
14873 					}
14874 				}
14875 			},
14876 
14877 			// Override commands to use the text formatter engine
14878 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
14879 				toggleFormat(command);
14880 			},
14881 
14882 			// Override commands to use the text formatter engine
14883 			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
14884 				toggleFormat(command, value);
14885 			},
14886 
14887 			FontSize : function(command, ui, value) {
14888 				var fontClasses, fontSizes;
14889 
14890 				// Convert font size 1-7 to styles
14891 				if (value >= 1 && value <= 7) {
14892 					fontSizes = tinymce.explode(settings.font_size_style_values);
14893 					fontClasses = tinymce.explode(settings.font_size_classes);
14894 
14895 					if (fontClasses)
14896 						value = fontClasses[value - 1] || value;
14897 					else
14898 						value = fontSizes[value - 1] || value;
14899 				}
14900 
14901 				toggleFormat(command, value);
14902 			},
14903 
14904 			RemoveFormat : function(command) {
14905 				formatter.remove(command);
14906 			},
14907 
14908 			mceBlockQuote : function(command) {
14909 				toggleFormat('blockquote');
14910 			},
14911 
14912 			FormatBlock : function(command, ui, value) {
14913 				return toggleFormat(value || 'p');
14914 			},
14915 
14916 			mceCleanup : function() {
14917 				var bookmark = selection.getBookmark();
14918 
14919 				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
14920 
14921 				selection.moveToBookmark(bookmark);
14922 			},
14923 
14924 			mceRemoveNode : function(command, ui, value) {
14925 				var node = value || selection.getNode();
14926 
14927 				// Make sure that the body node isn't removed
14928 				if (node != editor.getBody()) {
14929 					storeSelection();
14930 					editor.dom.remove(node, TRUE);
14931 					restoreSelection();
14932 				}
14933 			},
14934 
14935 			mceSelectNodeDepth : function(command, ui, value) {
14936 				var counter = 0;
14937 
14938 				dom.getParent(selection.getNode(), function(node) {
14939 					if (node.nodeType == 1 && counter++ == value) {
14940 						selection.select(node);
14941 						return FALSE;
14942 					}
14943 				}, editor.getBody());
14944 			},
14945 
14946 			mceSelectNode : function(command, ui, value) {
14947 				selection.select(value);
14948 			},
14949 
14950 			mceInsertContent : function(command, ui, value) {
14951 				var parser, serializer, parentNode, rootNode, fragment, args,
14952 					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
14953 
14954 				//selection.normalize();
14955 
14956 				// Setup parser and serializer
14957 				parser = editor.parser;
14958 				serializer = new tinymce.html.Serializer({}, editor.schema);
14959 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
14960 
14961 				// Run beforeSetContent handlers on the HTML to be inserted
14962 				args = {content: value, format: 'html'};
14963 				selection.onBeforeSetContent.dispatch(selection, args);
14964 				value = args.content;
14965 
14966 				// Add caret at end of contents if it's missing
14967 				if (value.indexOf('{$caret}') == -1)
14968 					value += '{$caret}';
14969 
14970 				// Replace the caret marker with a span bookmark element
14971 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
14972 
14973 				// Insert node maker where we will insert the new HTML and get it's parent
14974 				if (!selection.isCollapsed())
14975 					editor.getDoc().execCommand('Delete', false, null);
14976 
14977 				parentNode = selection.getNode();
14978 
14979 				// Parse the fragment within the context of the parent node
14980 				args = {context : parentNode.nodeName.toLowerCase()};
14981 				fragment = parser.parse(value, args);
14982 
14983 				// Move the caret to a more suitable location
14984 				node = fragment.lastChild;
14985 				if (node.attr('id') == 'mce_marker') {
14986 					marker = node;
14987 
14988 					for (node = node.prev; node; node = node.walk(true)) {
14989 						if (node.type == 3 || !dom.isBlock(node.name)) {
14990 							node.parent.insert(marker, node, node.name === 'br');
14991 							break;
14992 						}
14993 					}
14994 				}
14995 
14996 				// If parser says valid we can insert the contents into that parent
14997 				if (!args.invalid) {
14998 					value = serializer.serialize(fragment);
14999 
15000 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
15001 					node = parentNode.firstChild;
15002 					node2 = parentNode.lastChild;
15003 					if (!node || (node === node2 && node.nodeName === 'BR'))
15004 						dom.setHTML(parentNode, value);
15005 					else
15006 						selection.setContent(value);
15007 				} else {
15008 					// If the fragment was invalid within that context then we need
15009 					// to parse and process the parent it's inserted into
15010 
15011 					// Insert bookmark node and get the parent
15012 					selection.setContent(bookmarkHtml);
15013 					parentNode = editor.selection.getNode();
15014 					rootNode = editor.getBody();
15015 
15016 					// Opera will return the document node when selection is in root
15017 					if (parentNode.nodeType == 9)
15018 						parentNode = node = rootNode;
15019 					else
15020 						node = parentNode;
15021 
15022 					// Find the ancestor just before the root element
15023 					while (node !== rootNode) {
15024 						parentNode = node;
15025 						node = node.parentNode;
15026 					}
15027 
15028 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
15029 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
15030 					value = serializer.serialize(
15031 						parser.parse(
15032 							// Need to replace by using a function since $ in the contents would otherwise be a problem
15033 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
15034 								return serializer.serialize(fragment);
15035 							})
15036 						)
15037 					);
15038 
15039 					// Set the inner/outer HTML depending on if we are in the root or not
15040 					if (parentNode == rootNode)
15041 						dom.setHTML(rootNode, value);
15042 					else
15043 						dom.setOuterHTML(parentNode, value);
15044 				}
15045 
15046 				marker = dom.get('mce_marker');
15047 
15048 				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15049 				nodeRect = dom.getRect(marker);
15050 				viewPortRect = dom.getViewPort(editor.getWin());
15051 
15052 				// Check if node is out side the viewport if it is then scroll to it
15053 				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15054 					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15055 					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
15056 					viewportBodyElement.scrollLeft = nodeRect.x;
15057 					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15058 				}
15059 
15060 				// Move selection before marker and remove it
15061 				rng = dom.createRng();
15062 
15063 				// If previous sibling is a text node set the selection to the end of that node
15064 				node = marker.previousSibling;
15065 				if (node && node.nodeType == 3) {
15066 					rng.setStart(node, node.nodeValue.length);
15067 				} else {
15068 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15069 					rng.setStartBefore(marker);
15070 					rng.setEndBefore(marker);
15071 				}
15072 
15073 				// Remove the marker node and set the new range
15074 				dom.remove(marker);
15075 				selection.setRng(rng);
15076 
15077 				// Dispatch after event and add any visual elements needed
15078 				selection.onSetContent.dispatch(selection, args);
15079 				editor.addVisual();
15080 			},
15081 
15082 			mceInsertRawHTML : function(command, ui, value) {
15083 				selection.setContent('tiny_mce_marker');
15084 				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
15085 			},
15086 
15087 			mceToggleFormat : function(command, ui, value) {
15088 				toggleFormat(value);
15089 			},
15090 
15091 			mceSetContent : function(command, ui, value) {
15092 				editor.setContent(value);
15093 			},
15094 
15095 			'Indent,Outdent' : function(command) {
15096 				var intentValue, indentUnit, value;
15097 
15098 				// Setup indent level
15099 				intentValue = settings.indentation;
15100 				indentUnit = /[a-z%]+$/i.exec(intentValue);
15101 				intentValue = parseInt(intentValue);
15102 
15103 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15104 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15105 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15106 						formatter.apply('div');
15107 					}
15108 
15109 					each(selection.getSelectedBlocks(), function(element) {
15110 						if (command == 'outdent') {
15111 							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
15112 							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
15113 						} else
15114 							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
15115 					});
15116 				} else
15117 					execNativeCommand(command);
15118 			},
15119 
15120 			mceRepaint : function() {
15121 				var bookmark;
15122 
15123 				if (tinymce.isGecko) {
15124 					try {
15125 						storeSelection(TRUE);
15126 
15127 						if (selection.getSel())
15128 							selection.getSel().selectAllChildren(editor.getBody());
15129 
15130 						selection.collapse(TRUE);
15131 						restoreSelection();
15132 					} catch (ex) {
15133 						// Ignore
15134 					}
15135 				}
15136 			},
15137 
15138 			mceToggleFormat : function(command, ui, value) {
15139 				formatter.toggle(value);
15140 			},
15141 
15142 			InsertHorizontalRule : function() {
15143 				editor.execCommand('mceInsertContent', false, '<hr />');
15144 			},
15145 
15146 			mceToggleVisualAid : function() {
15147 				editor.hasVisual = !editor.hasVisual;
15148 				editor.addVisual();
15149 			},
15150 
15151 			mceReplaceContent : function(command, ui, value) {
15152 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
15153 			},
15154 
15155 			mceInsertLink : function(command, ui, value) {
15156 				var anchor;
15157 
15158 				if (typeof(value) == 'string')
15159 					value = {href : value};
15160 
15161 				anchor = dom.getParent(selection.getNode(), 'a');
15162 
15163 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15164 				value.href = value.href.replace(' ', '%20');
15165 
15166 				// Remove existing links if there could be child links or that the href isn't specified
15167 				if (!anchor || !value.href) {
15168 					formatter.remove('link');
15169 				}		
15170 
15171 				// Apply new link to selection
15172 				if (value.href) {
15173 					formatter.apply('link', value, anchor);
15174 				}
15175 			},
15176 
15177 			selectAll : function() {
15178 				var root = dom.getRoot(), rng = dom.createRng();
15179 
15180 				rng.setStart(root, 0);
15181 				rng.setEnd(root, root.childNodes.length);
15182 
15183 				editor.selection.setRng(rng);
15184 			}
15185 		});
15186 
15187 		// Add queryCommandState overrides
15188 		addCommands({
15189 			// Override justify commands
15190 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15191 				var name = 'align' + command.substring(7);
15192 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15193 				var matches = tinymce.map(nodes, function(node) {
15194 					return !!formatter.matchNode(node, name);
15195 				});
15196 				return tinymce.inArray(matches, TRUE) !== -1;
15197 			},
15198 
15199 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15200 				return isFormatMatch(command);
15201 			},
15202 
15203 			mceBlockQuote : function() {
15204 				return isFormatMatch('blockquote');
15205 			},
15206 
15207 			Outdent : function() {
15208 				var node;
15209 
15210 				if (settings.inline_styles) {
15211 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15212 						return TRUE;
15213 
15214 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15215 						return TRUE;
15216 				}
15217 
15218 				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
15219 			},
15220 
15221 			'InsertUnorderedList,InsertOrderedList' : function(command) {
15222 				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
15223 			}
15224 		}, 'state');
15225 
15226 		// Add queryCommandValue overrides
15227 		addCommands({
15228 			'FontSize,FontName' : function(command) {
15229 				var value = 0, parent;
15230 
15231 				if (parent = dom.getParent(selection.getNode(), 'span')) {
15232 					if (command == 'fontsize')
15233 						value = parent.style.fontSize;
15234 					else
15235 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15236 				}
15237 
15238 				return value;
15239 			}
15240 		}, 'value');
15241 
15242 		// Add undo manager logic
15243 		addCommands({
15244 			Undo : function() {
15245 				editor.undoManager.undo();
15246 			},
15247 
15248 			Redo : function() {
15249 				editor.undoManager.redo();
15250 			}
15251 		});
15252 	};
15253 })(tinymce);
15254 
15255 (function(tinymce) {
15256 	var Dispatcher = tinymce.util.Dispatcher;
15257 
15258 	tinymce.UndoManager = function(editor) {
15259 		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
15260 
15261 		function getContent() {
15262 			// Remove whitespace before/after and remove pure bogus nodes
15263 			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
15264 		};
15265 
15266 		function addNonTypingUndoLevel() {
15267 			self.typing = false;
15268 			self.add();
15269 		};
15270 
15271 		// Create event instances
15272 		onBeforeAdd = new Dispatcher(self);
15273 		onAdd       = new Dispatcher(self);
15274 		onUndo      = new Dispatcher(self);
15275 		onRedo      = new Dispatcher(self);
15276 
15277 		// Pass though onAdd event from UndoManager to Editor as onChange
15278 		onAdd.add(function(undoman, level) {
15279 			if (undoman.hasUndo())
15280 				return editor.onChange.dispatch(editor, level, undoman);
15281 		});
15282 
15283 		// Pass though onUndo event from UndoManager to Editor
15284 		onUndo.add(function(undoman, level) {
15285 			return editor.onUndo.dispatch(editor, level, undoman);
15286 		});
15287 
15288 		// Pass though onRedo event from UndoManager to Editor
15289 		onRedo.add(function(undoman, level) {
15290 			return editor.onRedo.dispatch(editor, level, undoman);
15291 		});
15292 
15293 		// Add initial undo level when the editor is initialized
15294 		editor.onInit.add(function() {
15295 			self.add();
15296 		});
15297 
15298 		// Get position before an execCommand is processed
15299 		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
15300 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15301 				self.beforeChange();
15302 			}
15303 		});
15304 
15305 		// Add undo level after an execCommand call was made
15306 		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
15307 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15308 				self.add();
15309 			}
15310 		});
15311 
15312 		// Add undo level on save contents, drag end and blur/focusout
15313 		editor.onSaveContent.add(addNonTypingUndoLevel);
15314 		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15315 		editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
15316 			if (!editor.removed && self.typing) {
15317 				addNonTypingUndoLevel();
15318 			}
15319 		});
15320 
15321 		editor.onKeyUp.add(function(editor, e) {
15322 			var keyCode = e.keyCode;
15323 
15324 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
15325 				addNonTypingUndoLevel();
15326 			}
15327 		});
15328 
15329 		editor.onKeyDown.add(function(editor, e) {
15330 			var keyCode = e.keyCode;
15331 
15332 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
15333 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
15334 				if (self.typing) {
15335 					addNonTypingUndoLevel();
15336 				}
15337 
15338 				return;
15339 			}
15340 
15341 			// If key isn't shift,ctrl,alt,capslock,metakey
15342 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
15343 				self.beforeChange();
15344 				self.typing = true;
15345 				self.add();
15346 			}
15347 		});
15348 
15349 		editor.onMouseDown.add(function(editor, e) {
15350 			if (self.typing) {
15351 				addNonTypingUndoLevel();
15352 			}
15353 		});
15354 
15355 		// Add keyboard shortcuts for undo/redo keys
15356 		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
15357 		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
15358 
15359 		self = {
15360 			// Explose for debugging reasons
15361 			data : data,
15362 
15363 			typing : false,
15364 			
15365 			onBeforeAdd: onBeforeAdd,
15366 
15367 			onAdd : onAdd,
15368 
15369 			onUndo : onUndo,
15370 
15371 			onRedo : onRedo,
15372 
15373 			beforeChange : function() {
15374 				beforeBookmark = editor.selection.getBookmark(2, true);
15375 			},
15376 
15377 			add : function(level) {
15378 				var i, settings = editor.settings, lastLevel;
15379 
15380 				level = level || {};
15381 				level.content = getContent();
15382 				
15383 				self.onBeforeAdd.dispatch(self, level);
15384 
15385 				// Add undo level if needed
15386 				lastLevel = data[index];
15387 				if (lastLevel && lastLevel.content == level.content)
15388 					return null;
15389 
15390 				// Set before bookmark on previous level
15391 				if (data[index])
15392 					data[index].beforeBookmark = beforeBookmark;
15393 
15394 				// Time to compress
15395 				if (settings.custom_undo_redo_levels) {
15396 					if (data.length > settings.custom_undo_redo_levels) {
15397 						for (i = 0; i < data.length - 1; i++)
15398 							data[i] = data[i + 1];
15399 
15400 						data.length--;
15401 						index = data.length;
15402 					}
15403 				}
15404 
15405 				// Get a non intrusive normalized bookmark
15406 				level.bookmark = editor.selection.getBookmark(2, true);
15407 
15408 				// Crop array if needed
15409 				if (index < data.length - 1)
15410 					data.length = index + 1;
15411 
15412 				data.push(level);
15413 				index = data.length - 1;
15414 
15415 				self.onAdd.dispatch(self, level);
15416 				editor.isNotDirty = 0;
15417 
15418 				return level;
15419 			},
15420 
15421 			undo : function() {
15422 				var level, i;
15423 
15424 				if (self.typing) {
15425 					self.add();
15426 					self.typing = false;
15427 				}
15428 
15429 				if (index > 0) {
15430 					level = data[--index];
15431 
15432 					editor.setContent(level.content, {format : 'raw'});
15433 					editor.selection.moveToBookmark(level.beforeBookmark);
15434 
15435 					self.onUndo.dispatch(self, level);
15436 				}
15437 
15438 				return level;
15439 			},
15440 
15441 			redo : function() {
15442 				var level;
15443 
15444 				if (index < data.length - 1) {
15445 					level = data[++index];
15446 
15447 					editor.setContent(level.content, {format : 'raw'});
15448 					editor.selection.moveToBookmark(level.bookmark);
15449 
15450 					self.onRedo.dispatch(self, level);
15451 				}
15452 
15453 				return level;
15454 			},
15455 
15456 			clear : function() {
15457 				data = [];
15458 				index = 0;
15459 				self.typing = false;
15460 			},
15461 
15462 			hasUndo : function() {
15463 				return index > 0 || this.typing;
15464 			},
15465 
15466 			hasRedo : function() {
15467 				return index < data.length - 1 && !this.typing;
15468 			}
15469 		};
15470 
15471 		return self;
15472 	};
15473 })(tinymce);
15474 
15475 tinymce.ForceBlocks = function(editor) {
15476 	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
15477 
15478 	function addRootBlocks() {
15479 		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
15480 
15481 		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
15482 			return;
15483 
15484 		// Check if node is wrapped in block
15485 		while (node && node != rootNode) {
15486 			if (blockElements[node.nodeName])
15487 				return;
15488 
15489 			node = node.parentNode;
15490 		}
15491 
15492 		// Get current selection
15493 		rng = selection.getRng();
15494 		if (rng.setStart) {
15495 			startContainer = rng.startContainer;
15496 			startOffset = rng.startOffset;
15497 			endContainer = rng.endContainer;
15498 			endOffset = rng.endOffset;
15499 		} else {
15500 			// Force control range into text range
15501 			if (rng.item) {
15502 				node = rng.item(0);
15503 				rng = editor.getDoc().body.createTextRange();
15504 				rng.moveToElementText(node);
15505 			}
15506 
15507 			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
15508 			tmpRng = rng.duplicate();
15509 			tmpRng.collapse(true);
15510 			startOffset = tmpRng.move('character', offset) * -1;
15511 
15512 			if (!tmpRng.collapsed) {
15513 				tmpRng = rng.duplicate();
15514 				tmpRng.collapse(false);
15515 				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
15516 			}
15517 		}
15518 
15519 		// Wrap non block elements and text nodes
15520 		node = rootNode.firstChild;
15521 		while (node) {
15522 			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
15523 				if (!rootBlockNode) {
15524 					rootBlockNode = dom.create(settings.forced_root_block);
15525 					node.parentNode.insertBefore(rootBlockNode, node);
15526 					wrapped = true;
15527 				}
15528 
15529 				tempNode = node;
15530 				node = node.nextSibling;
15531 				rootBlockNode.appendChild(tempNode);
15532 			} else {
15533 				rootBlockNode = null;
15534 				node = node.nextSibling;
15535 			}
15536 		}
15537 
15538 		if (wrapped) {
15539 			if (rng.setStart) {
15540 				rng.setStart(startContainer, startOffset);
15541 				rng.setEnd(endContainer, endOffset);
15542 				selection.setRng(rng);
15543 			} else {
15544 				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
15545 				if (isInEditorDocument) {
15546 					try {
15547 						rng = editor.getDoc().body.createTextRange();
15548 						rng.moveToElementText(rootNode);
15549 						rng.collapse(true);
15550 						rng.moveStart('character', startOffset);
15551 
15552 						if (endOffset > 0)
15553 							rng.moveEnd('character', endOffset);
15554 
15555 						rng.select();
15556 					} catch (ex) {
15557 						// Ignore
15558 					}
15559 				}
15560 			}
15561 
15562 			editor.nodeChanged();
15563 		}
15564 	};
15565 
15566 	// Force root blocks
15567 	if (settings.forced_root_block) {
15568 		editor.onKeyUp.add(addRootBlocks);
15569 		editor.onNodeChange.add(addRootBlocks);
15570 	}
15571 };
15572 
15573 (function(tinymce) {
15574 	// Shorten names
15575 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15576 
15577 	tinymce.create('tinymce.ControlManager', {
15578 		ControlManager : function(ed, s) {
15579 			var t = this, i;
15580 
15581 			s = s || {};
15582 			t.editor = ed;
15583 			t.controls = {};
15584 			t.onAdd = new tinymce.util.Dispatcher(t);
15585 			t.onPostRender = new tinymce.util.Dispatcher(t);
15586 			t.prefix = s.prefix || ed.id + '_';
15587 			t._cls = {};
15588 
15589 			t.onPostRender.add(function() {
15590 				each(t.controls, function(c) {
15591 					c.postRender();
15592 				});
15593 			});
15594 		},
15595 
15596 		get : function(id) {
15597 			return this.controls[this.prefix + id] || this.controls[id];
15598 		},
15599 
15600 		setActive : function(id, s) {
15601 			var c = null;
15602 
15603 			if (c = this.get(id))
15604 				c.setActive(s);
15605 
15606 			return c;
15607 		},
15608 
15609 		setDisabled : function(id, s) {
15610 			var c = null;
15611 
15612 			if (c = this.get(id))
15613 				c.setDisabled(s);
15614 
15615 			return c;
15616 		},
15617 
15618 		add : function(c) {
15619 			var t = this;
15620 
15621 			if (c) {
15622 				t.controls[c.id] = c;
15623 				t.onAdd.dispatch(c, t);
15624 			}
15625 
15626 			return c;
15627 		},
15628 
15629 		createControl : function(name) {
15630 			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
15631 
15632 			// Build control factory cache
15633 			if (!self.controlFactories) {
15634 				self.controlFactories = [];
15635 				each(editor.plugins, function(plugin) {
15636 					if (plugin.createControl) {
15637 						self.controlFactories.push(plugin);
15638 					}
15639 				});
15640 			}
15641 
15642 			// Create controls by asking cached factories
15643 			factories = self.controlFactories;
15644 			for (i = 0, l = factories.length; i < l; i++) {
15645 				ctrl = factories[i].createControl(name, self);
15646 
15647 				if (ctrl) {
15648 					return self.add(ctrl);
15649 				}
15650 			}
15651 
15652 			// Create sepearator
15653 			if (name === "|" || name === "separator") {
15654 				return self.createSeparator();
15655 			}
15656 
15657 			// Create control from button collection
15658 			if (editor.buttons && (ctrl = editor.buttons[name])) {
15659 				return self.createButton(name, ctrl);
15660 			}
15661 
15662 			return self.add(ctrl);
15663 		},
15664 
15665 		createDropMenu : function(id, s, cc) {
15666 			var t = this, ed = t.editor, c, bm, v, cls;
15667 
15668 			s = extend({
15669 				'class' : 'mceDropDown',
15670 				constrain : ed.settings.constrain_menus
15671 			}, s);
15672 
15673 			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
15674 			if (v = ed.getParam('skin_variant'))
15675 				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
15676 
15677 			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
15678 
15679 			id = t.prefix + id;
15680 			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
15681 			c = t.controls[id] = new cls(id, s);
15682 			c.onAddItem.add(function(c, o) {
15683 				var s = o.settings;
15684 
15685 				s.title = ed.getLang(s.title, s.title);
15686 
15687 				if (!s.onclick) {
15688 					s.onclick = function(v) {
15689 						if (s.cmd)
15690 							ed.execCommand(s.cmd, s.ui || false, s.value);
15691 					};
15692 				}
15693 			});
15694 
15695 			ed.onRemove.add(function() {
15696 				c.destroy();
15697 			});
15698 
15699 			// Fix for bug #1897785, #1898007
15700 			if (tinymce.isIE) {
15701 				c.onShowMenu.add(function() {
15702 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15703 					ed.focus();
15704 
15705 					bm = ed.selection.getBookmark(1);
15706 				});
15707 
15708 				c.onHideMenu.add(function() {
15709 					if (bm) {
15710 						ed.selection.moveToBookmark(bm);
15711 						bm = 0;
15712 					}
15713 				});
15714 			}
15715 
15716 			return t.add(c);
15717 		},
15718 
15719 		createListBox : function(id, s, cc) {
15720 			var t = this, ed = t.editor, cmd, c, cls;
15721 
15722 			if (t.get(id))
15723 				return null;
15724 
15725 			s.title = ed.translate(s.title);
15726 			s.scope = s.scope || ed;
15727 
15728 			if (!s.onselect) {
15729 				s.onselect = function(v) {
15730 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15731 				};
15732 			}
15733 
15734 			s = extend({
15735 				title : s.title,
15736 				'class' : 'mce_' + id,
15737 				scope : s.scope,
15738 				control_manager : t
15739 			}, s);
15740 
15741 			id = t.prefix + id;
15742 
15743 
15744 			function useNativeListForAccessibility(ed) {
15745 				return ed.settings.use_accessible_selects && !tinymce.isGecko
15746 			}
15747 
15748 			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
15749 				c = new tinymce.ui.NativeListBox(id, s);
15750 			else {
15751 				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
15752 				c = new cls(id, s, ed);
15753 			}
15754 
15755 			t.controls[id] = c;
15756 
15757 			// Fix focus problem in Safari
15758 			if (tinymce.isWebKit) {
15759 				c.onPostRender.add(function(c, n) {
15760 					// Store bookmark on mousedown
15761 					Event.add(n, 'mousedown', function() {
15762 						ed.bookmark = ed.selection.getBookmark(1);
15763 					});
15764 
15765 					// Restore on focus, since it might be lost
15766 					Event.add(n, 'focus', function() {
15767 						ed.selection.moveToBookmark(ed.bookmark);
15768 						ed.bookmark = null;
15769 					});
15770 				});
15771 			}
15772 
15773 			if (c.hideMenu)
15774 				ed.onMouseDown.add(c.hideMenu, c);
15775 
15776 			return t.add(c);
15777 		},
15778 
15779 		createButton : function(id, s, cc) {
15780 			var t = this, ed = t.editor, o, c, cls;
15781 
15782 			if (t.get(id))
15783 				return null;
15784 
15785 			s.title = ed.translate(s.title);
15786 			s.label = ed.translate(s.label);
15787 			s.scope = s.scope || ed;
15788 
15789 			if (!s.onclick && !s.menu_button) {
15790 				s.onclick = function() {
15791 					ed.execCommand(s.cmd, s.ui || false, s.value);
15792 				};
15793 			}
15794 
15795 			s = extend({
15796 				title : s.title,
15797 				'class' : 'mce_' + id,
15798 				unavailable_prefix : ed.getLang('unavailable', ''),
15799 				scope : s.scope,
15800 				control_manager : t
15801 			}, s);
15802 
15803 			id = t.prefix + id;
15804 
15805 			if (s.menu_button) {
15806 				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
15807 				c = new cls(id, s, ed);
15808 				ed.onMouseDown.add(c.hideMenu, c);
15809 			} else {
15810 				cls = t._cls.button || tinymce.ui.Button;
15811 				c = new cls(id, s, ed);
15812 			}
15813 
15814 			return t.add(c);
15815 		},
15816 
15817 		createMenuButton : function(id, s, cc) {
15818 			s = s || {};
15819 			s.menu_button = 1;
15820 
15821 			return this.createButton(id, s, cc);
15822 		},
15823 
15824 		createSplitButton : function(id, s, cc) {
15825 			var t = this, ed = t.editor, cmd, c, cls;
15826 
15827 			if (t.get(id))
15828 				return null;
15829 
15830 			s.title = ed.translate(s.title);
15831 			s.scope = s.scope || ed;
15832 
15833 			if (!s.onclick) {
15834 				s.onclick = function(v) {
15835 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15836 				};
15837 			}
15838 
15839 			if (!s.onselect) {
15840 				s.onselect = function(v) {
15841 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15842 				};
15843 			}
15844 
15845 			s = extend({
15846 				title : s.title,
15847 				'class' : 'mce_' + id,
15848 				scope : s.scope,
15849 				control_manager : t
15850 			}, s);
15851 
15852 			id = t.prefix + id;
15853 			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
15854 			c = t.add(new cls(id, s, ed));
15855 			ed.onMouseDown.add(c.hideMenu, c);
15856 
15857 			return c;
15858 		},
15859 
15860 		createColorSplitButton : function(id, s, cc) {
15861 			var t = this, ed = t.editor, cmd, c, cls, bm;
15862 
15863 			if (t.get(id))
15864 				return null;
15865 
15866 			s.title = ed.translate(s.title);
15867 			s.scope = s.scope || ed;
15868 
15869 			if (!s.onclick) {
15870 				s.onclick = function(v) {
15871 					if (tinymce.isIE)
15872 						bm = ed.selection.getBookmark(1);
15873 
15874 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15875 				};
15876 			}
15877 
15878 			if (!s.onselect) {
15879 				s.onselect = function(v) {
15880 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15881 				};
15882 			}
15883 
15884 			s = extend({
15885 				title : s.title,
15886 				'class' : 'mce_' + id,
15887 				'menu_class' : ed.getParam('skin') + 'Skin',
15888 				scope : s.scope,
15889 				more_colors_title : ed.getLang('more_colors')
15890 			}, s);
15891 
15892 			id = t.prefix + id;
15893 			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
15894 			c = new cls(id, s, ed);
15895 			ed.onMouseDown.add(c.hideMenu, c);
15896 
15897 			// Remove the menu element when the editor is removed
15898 			ed.onRemove.add(function() {
15899 				c.destroy();
15900 			});
15901 
15902 			// Fix for bug #1897785, #1898007
15903 			if (tinymce.isIE) {
15904 				c.onShowMenu.add(function() {
15905 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15906 					ed.focus();
15907 					bm = ed.selection.getBookmark(1);
15908 				});
15909 
15910 				c.onHideMenu.add(function() {
15911 					if (bm) {
15912 						ed.selection.moveToBookmark(bm);
15913 						bm = 0;
15914 					}
15915 				});
15916 			}
15917 
15918 			return t.add(c);
15919 		},
15920 
15921 		createToolbar : function(id, s, cc) {
15922 			var c, t = this, cls;
15923 
15924 			id = t.prefix + id;
15925 			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
15926 			c = new cls(id, s, t.editor);
15927 
15928 			if (t.get(id))
15929 				return null;
15930 
15931 			return t.add(c);
15932 		},
15933 		
15934 		createToolbarGroup : function(id, s, cc) {
15935 			var c, t = this, cls;
15936 			id = t.prefix + id;
15937 			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
15938 			c = new cls(id, s, t.editor);
15939 			
15940 			if (t.get(id))
15941 				return null;
15942 			
15943 			return t.add(c);
15944 		},
15945 
15946 		createSeparator : function(cc) {
15947 			var cls = cc || this._cls.separator || tinymce.ui.Separator;
15948 
15949 			return new cls();
15950 		},
15951 
15952 		setControlType : function(n, c) {
15953 			return this._cls[n.toLowerCase()] = c;
15954 		},
15955 	
15956 		destroy : function() {
15957 			each(this.controls, function(c) {
15958 				c.destroy();
15959 			});
15960 
15961 			this.controls = null;
15962 		}
15963 	});
15964 })(tinymce);
15965 
15966 (function(tinymce) {
15967 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
15968 
15969 	tinymce.create('tinymce.WindowManager', {
15970 		WindowManager : function(ed) {
15971 			var t = this;
15972 
15973 			t.editor = ed;
15974 			t.onOpen = new Dispatcher(t);
15975 			t.onClose = new Dispatcher(t);
15976 			t.params = {};
15977 			t.features = {};
15978 		},
15979 
15980 		open : function(s, p) {
15981 			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
15982 
15983 			// Default some options
15984 			s = s || {};
15985 			p = p || {};
15986 			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
15987 			sh = isOpera ? vp.h : screen.height;
15988 			s.name = s.name || 'mc_' + new Date().getTime();
15989 			s.width = parseInt(s.width || 320);
15990 			s.height = parseInt(s.height || 240);
15991 			s.resizable = true;
15992 			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
15993 			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
15994 			p.inline = false;
15995 			p.mce_width = s.width;
15996 			p.mce_height = s.height;
15997 			p.mce_auto_focus = s.auto_focus;
15998 
15999 			if (mo) {
16000 				if (isIE) {
16001 					s.center = true;
16002 					s.help = false;
16003 					s.dialogWidth = s.width + 'px';
16004 					s.dialogHeight = s.height + 'px';
16005 					s.scroll = s.scrollbars || false;
16006 				}
16007 			}
16008 
16009 			// Build features string
16010 			each(s, function(v, k) {
16011 				if (tinymce.is(v, 'boolean'))
16012 					v = v ? 'yes' : 'no';
16013 
16014 				if (!/^(name|url)$/.test(k)) {
16015 					if (isIE && mo)
16016 						f += (f ? ';' : '') + k + ':' + v;
16017 					else
16018 						f += (f ? ',' : '') + k + '=' + v;
16019 				}
16020 			});
16021 
16022 			t.features = s;
16023 			t.params = p;
16024 			t.onOpen.dispatch(t, s, p);
16025 
16026 			u = s.url || s.file;
16027 			u = tinymce._addVer(u);
16028 
16029 			try {
16030 				if (isIE && mo) {
16031 					w = 1;
16032 					window.showModalDialog(u, window, f);
16033 				} else
16034 					w = window.open(u, s.name, f);
16035 			} catch (ex) {
16036 				// Ignore
16037 			}
16038 
16039 			if (!w)
16040 				alert(t.editor.getLang('popup_blocked'));
16041 		},
16042 
16043 		close : function(w) {
16044 			w.close();
16045 			this.onClose.dispatch(this);
16046 		},
16047 
16048 		createInstance : function(cl, a, b, c, d, e) {
16049 			var f = tinymce.resolve(cl);
16050 
16051 			return new f(a, b, c, d, e);
16052 		},
16053 
16054 		confirm : function(t, cb, s, w) {
16055 			w = w || window;
16056 
16057 			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
16058 		},
16059 
16060 		alert : function(tx, cb, s, w) {
16061 			var t = this;
16062 
16063 			w = w || window;
16064 			w.alert(t._decode(t.editor.getLang(tx, tx)));
16065 
16066 			if (cb)
16067 				cb.call(s || t);
16068 		},
16069 
16070 		resizeBy : function(dw, dh, win) {
16071 			win.resizeBy(dw, dh);
16072 		},
16073 
16074 		// Internal functions
16075 
16076 		_decode : function(s) {
16077 			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
16078 		}
16079 	});
16080 }(tinymce));
16081 (function(tinymce) {
16082 	tinymce.Formatter = function(ed) {
16083 		var formats = {},
16084 			each = tinymce.each,
16085 			dom = ed.dom,
16086 			selection = ed.selection,
16087 			TreeWalker = tinymce.dom.TreeWalker,
16088 			rangeUtils = new tinymce.dom.RangeUtils(dom),
16089 			isValid = ed.schema.isValidChild,
16090 			isBlock = dom.isBlock,
16091 			forcedRootBlock = ed.settings.forced_root_block,
16092 			nodeIndex = dom.nodeIndex,
16093 			INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
16094 			MCE_ATTR_RE = /^(src|href|style)$/,
16095 			FALSE = false,
16096 			TRUE = true,
16097 			formatChangeData,
16098 			undef,
16099 			getContentEditable = dom.getContentEditable;
16100 
16101 		function isArray(obj) {
16102 			return obj instanceof Array;
16103 		};
16104 
16105 		function getParents(node, selector) {
16106 			return dom.getParents(node, selector, dom.getRoot());
16107 		};
16108 
16109 		function isCaretNode(node) {
16110 			return node.nodeType === 1 && node.id === '_mce_caret';
16111 		};
16112 
16113 		function defaultFormats() {
16114 			register({
16115 				alignleft : [
16116 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
16117 					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
16118 				],
16119 
16120 				aligncenter : [
16121 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16122 					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
16123 					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
16124 				],
16125 
16126 				alignright : [
16127 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
16128 					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
16129 				],
16130 
16131 				alignfull : [
16132 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
16133 				],
16134 
16135 				bold : [
16136 					{inline : 'strong', remove : 'all'},
16137 					{inline : 'span', styles : {fontWeight : 'bold'}},
16138 					{inline : 'b', remove : 'all'}
16139 				],
16140 
16141 				italic : [
16142 					{inline : 'em', remove : 'all'},
16143 					{inline : 'span', styles : {fontStyle : 'italic'}},
16144 					{inline : 'i', remove : 'all'}
16145 				],
16146 
16147 				underline : [
16148 					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
16149 					{inline : 'u', remove : 'all'}
16150 				],
16151 
16152 				strikethrough : [
16153 					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
16154 					{inline : 'strike', remove : 'all'}
16155 				],
16156 
16157 				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
16158 				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
16159 				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
16160 				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
16161 				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
16162 				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
16163 				subscript : {inline : 'sub'},
16164 				superscript : {inline : 'sup'},
16165 
16166 				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
16167 					onmatch : function(node) {
16168 						return true;
16169 					},
16170 
16171 					onformat : function(elm, fmt, vars) {
16172 						each(vars, function(value, key) {
16173 							dom.setAttrib(elm, key, value);
16174 						});
16175 					}
16176 				},
16177 
16178 				removeformat : [
16179 					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
16180 					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
16181 					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
16182 				]
16183 			});
16184 
16185 			// Register default block formats
16186 			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
16187 				register(name, {block : name, remove : 'all'});
16188 			});
16189 
16190 			// Register user defined formats
16191 			register(ed.settings.formats);
16192 		};
16193 
16194 		function addKeyboardShortcuts() {
16195 			// Add some inline shortcuts
16196 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
16197 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
16198 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
16199 
16200 			// BlockFormat shortcuts keys
16201 			for (var i = 1; i <= 6; i++) {
16202 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
16203 			}
16204 
16205 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
16206 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
16207 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
16208 		};
16209 
16210 		// Public functions
16211 
16212 		function get(name) {
16213 			return name ? formats[name] : formats;
16214 		};
16215 
16216 		function register(name, format) {
16217 			if (name) {
16218 				if (typeof(name) !== 'string') {
16219 					each(name, function(format, name) {
16220 						register(name, format);
16221 					});
16222 				} else {
16223 					// Force format into array and add it to internal collection
16224 					format = format.length ? format : [format];
16225 
16226 					each(format, function(format) {
16227 						// Set deep to false by default on selector formats this to avoid removing
16228 						// alignment on images inside paragraphs when alignment is changed on paragraphs
16229 						if (format.deep === undef)
16230 							format.deep = !format.selector;
16231 
16232 						// Default to true
16233 						if (format.split === undef)
16234 							format.split = !format.selector || format.inline;
16235 
16236 						// Default to true
16237 						if (format.remove === undef && format.selector && !format.inline)
16238 							format.remove = 'none';
16239 
16240 						// Mark format as a mixed format inline + block level
16241 						if (format.selector && format.inline) {
16242 							format.mixed = true;
16243 							format.block_expand = true;
16244 						}
16245 
16246 						// Split classes if needed
16247 						if (typeof(format.classes) === 'string')
16248 							format.classes = format.classes.split(/\s+/);
16249 					});
16250 
16251 					formats[name] = format;
16252 				}
16253 			}
16254 		};
16255 
16256 		var getTextDecoration = function(node) {
16257 			var decoration;
16258 
16259 			ed.dom.getParent(node, function(n) {
16260 				decoration = ed.dom.getStyle(n, 'text-decoration');
16261 				return decoration && decoration !== 'none';
16262 			});
16263 
16264 			return decoration;
16265 		};
16266 
16267 		var processUnderlineAndColor = function(node) {
16268 			var textDecoration;
16269 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
16270 				textDecoration = getTextDecoration(node.parentNode);
16271 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
16272 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
16273 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
16274 					ed.dom.setStyle(node, 'text-decoration', null);
16275 				}
16276 			}
16277 		};
16278 
16279 		function apply(name, vars, node) {
16280 			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
16281 
16282 			function setElementFormat(elm, fmt) {
16283 				fmt = fmt || format;
16284 
16285 				if (elm) {
16286 					if (fmt.onformat) {
16287 						fmt.onformat(elm, fmt, vars, node);
16288 					}
16289 
16290 					each(fmt.styles, function(value, name) {
16291 						dom.setStyle(elm, name, replaceVars(value, vars));
16292 					});
16293 
16294 					each(fmt.attributes, function(value, name) {
16295 						dom.setAttrib(elm, name, replaceVars(value, vars));
16296 					});
16297 
16298 					each(fmt.classes, function(value) {
16299 						value = replaceVars(value, vars);
16300 
16301 						if (!dom.hasClass(elm, value))
16302 							dom.addClass(elm, value);
16303 					});
16304 				}
16305 			};
16306 			function adjustSelectionToVisibleSelection() {
16307 				function findSelectionEnd(start, end) {
16308 					var walker = new TreeWalker(end);
16309 					for (node = walker.current(); node; node = walker.prev()) {
16310 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
16311 							return node;
16312 						}
16313 					}
16314 				};
16315 
16316 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
16317 				// as this isn't visible to the user.
16318 				var rng = ed.selection.getRng();
16319 				var start = rng.startContainer;
16320 				var end = rng.endContainer;
16321 
16322 				if (start != end && rng.endOffset === 0) {
16323 					var newEnd = findSelectionEnd(start, end);
16324 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
16325 
16326 					rng.setEnd(newEnd, endOffset);
16327 				}
16328 
16329 				return rng;
16330 			}
16331 			
16332 			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
16333 				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
16334 				
16335 				// find the index of the first child list.
16336 				each(node.childNodes, function(n, index) {
16337 					if (n.nodeName === "UL" || n.nodeName === "OL") {
16338 						listIndex = index;
16339 						list = n;
16340 						return false;
16341 					}
16342 				});
16343 				
16344 				// get the index of the bookmarks
16345 				each(node.childNodes, function(n, index) {
16346 					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
16347 						if (n.id == bookmark.id + "_start") {
16348 							startIndex = index;
16349 						} else if (n.id == bookmark.id + "_end") {
16350 							endIndex = index;
16351 						}
16352 					}
16353 				});
16354 				
16355 				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
16356 				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
16357 					each(tinymce.grep(node.childNodes), process);
16358 					return 0;
16359 				} else {
16360 					currentWrapElm = dom.clone(wrapElm, FALSE);
16361 
16362 					// create a list of the nodes on the same side of the list as the selection
16363 					each(tinymce.grep(node.childNodes), function(n, index) {
16364 						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
16365 							nodes.push(n); 
16366 							n.parentNode.removeChild(n);
16367 						}
16368 					});
16369 
16370 					// insert the wrapping element either before or after the list.
16371 					if (startIndex < listIndex) {
16372 						node.insertBefore(currentWrapElm, list);
16373 					} else if (startIndex > listIndex) {
16374 						node.insertBefore(currentWrapElm, list.nextSibling);
16375 					}
16376 					
16377 					// add the new nodes to the list.
16378 					newWrappers.push(currentWrapElm);
16379 
16380 					each(nodes, function(node) {
16381 						currentWrapElm.appendChild(node);
16382 					});
16383 
16384 					return currentWrapElm;
16385 				}
16386 			};
16387 
16388 			function applyRngStyle(rng, bookmark, node_specific) {
16389 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
16390 
16391 				// Setup wrapper element
16392 				wrapName = format.inline || format.block;
16393 				wrapElm = dom.create(wrapName);
16394 				setElementFormat(wrapElm);
16395 
16396 				rangeUtils.walk(rng, function(nodes) {
16397 					var currentWrapElm;
16398 
16399 					function process(node) {
16400 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
16401 
16402 						lastContentEditable = contentEditable;
16403 						nodeName = node.nodeName.toLowerCase();
16404 						parentName = node.parentNode.nodeName.toLowerCase();
16405 
16406 						// Node has a contentEditable value
16407 						if (node.nodeType === 1 && getContentEditable(node)) {
16408 							lastContentEditable = contentEditable;
16409 							contentEditable = getContentEditable(node) === "true";
16410 							hasContentEditableState = true; // We don't want to wrap the container only it's children
16411 						}
16412 
16413 						// Stop wrapping on br elements
16414 						if (isEq(nodeName, 'br')) {
16415 							currentWrapElm = 0;
16416 
16417 							// Remove any br elements when we wrap things
16418 							if (format.block)
16419 								dom.remove(node);
16420 
16421 							return;
16422 						}
16423 
16424 						// If node is wrapper type
16425 						if (format.wrapper && matchNode(node, name, vars)) {
16426 							currentWrapElm = 0;
16427 							return;
16428 						}
16429 
16430 						// Can we rename the block
16431 						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
16432 							node = dom.rename(node, wrapName);
16433 							setElementFormat(node);
16434 							newWrappers.push(node);
16435 							currentWrapElm = 0;
16436 							return;
16437 						}
16438 
16439 						// Handle selector patterns
16440 						if (format.selector) {
16441 							// Look for matching formats
16442 							each(formatList, function(format) {
16443 								// Check collapsed state if it exists
16444 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
16445 									return;
16446 								}
16447 
16448 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
16449 									setElementFormat(node, format);
16450 									found = true;
16451 								}
16452 							});
16453 
16454 							// Continue processing if a selector match wasn't found and a inline element is defined
16455 							if (!format.inline || found) {
16456 								currentWrapElm = 0;
16457 								return;
16458 							}
16459 						}
16460 
16461 						// Is it valid to wrap this item
16462 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
16463 								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
16464 							// Start wrapping
16465 							if (!currentWrapElm) {
16466 								// Wrap the node
16467 								currentWrapElm = dom.clone(wrapElm, FALSE);
16468 								node.parentNode.insertBefore(currentWrapElm, node);
16469 								newWrappers.push(currentWrapElm);
16470 							}
16471 
16472 							currentWrapElm.appendChild(node);
16473 						} else if (nodeName == 'li' && bookmark) {
16474 							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
16475 							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
16476 						} else {
16477 							// Start a new wrapper for possible children
16478 							currentWrapElm = 0;
16479 							
16480 							each(tinymce.grep(node.childNodes), process);
16481 
16482 							if (hasContentEditableState) {
16483 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16484 							}
16485 
16486 							// End the last wrapper
16487 							currentWrapElm = 0;
16488 						}
16489 					};
16490 
16491 					// Process siblings from range
16492 					each(nodes, process);
16493 				});
16494 
16495 				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
16496 				if (format.wrap_links === false) {
16497 					each(newWrappers, function(node) {
16498 						function process(node) {
16499 							var i, currentWrapElm, children;
16500 
16501 							if (node.nodeName === 'A') {
16502 								currentWrapElm = dom.clone(wrapElm, FALSE);
16503 								newWrappers.push(currentWrapElm);
16504 
16505 								children = tinymce.grep(node.childNodes);
16506 								for (i = 0; i < children.length; i++)
16507 									currentWrapElm.appendChild(children[i]);
16508 
16509 								node.appendChild(currentWrapElm);
16510 							}
16511 
16512 							each(tinymce.grep(node.childNodes), process);
16513 						};
16514 
16515 						process(node);
16516 					});
16517 				}
16518 
16519 				// Cleanup
16520 				
16521 				each(newWrappers, function(node) {
16522 					var childCount;
16523 
16524 					function getChildCount(node) {
16525 						var count = 0;
16526 
16527 						each(node.childNodes, function(node) {
16528 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
16529 								count++;
16530 						});
16531 
16532 						return count;
16533 					};
16534 
16535 					function mergeStyles(node) {
16536 						var child, clone;
16537 
16538 						each(node.childNodes, function(node) {
16539 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
16540 								child = node;
16541 								return FALSE; // break loop
16542 							}
16543 						});
16544 
16545 						// If child was found and of the same type as the current node
16546 						if (child && matchName(child, format)) {
16547 							clone = dom.clone(child, FALSE);
16548 							setElementFormat(clone);
16549 
16550 							dom.replace(clone, node, TRUE);
16551 							dom.remove(child, 1);
16552 						}
16553 
16554 						return clone || node;
16555 					};
16556 
16557 					childCount = getChildCount(node);
16558 
16559 					// Remove empty nodes but only if there is multiple wrappers and they are not block
16560 					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
16561 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
16562 						dom.remove(node, 1);
16563 						return;
16564 					}
16565 
16566 					if (format.inline || format.wrapper) {
16567 						// Merges the current node with it's children of similar type to reduce the number of elements
16568 						if (!format.exact && childCount === 1)
16569 							node = mergeStyles(node);
16570 
16571 						// Remove/merge children
16572 						each(formatList, function(format) {
16573 							// Merge all children of similar type will move styles from child to parent
16574 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
16575 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16576 							each(dom.select(format.inline, node), function(child) {
16577 								var parent;
16578 
16579 								// When wrap_links is set to false we don't want
16580 								// to remove the format on children within links
16581 								if (format.wrap_links === false) {
16582 									parent = child.parentNode;
16583 
16584 									do {
16585 										if (parent.nodeName === 'A')
16586 											return;
16587 									} while (parent = parent.parentNode);
16588 								}
16589 
16590 								removeFormat(format, vars, child, format.exact ? child : null);
16591 							});
16592 						});
16593 
16594 						// Remove child if direct parent is of same type
16595 						if (matchNode(node.parentNode, name, vars)) {
16596 							dom.remove(node, 1);
16597 							node = 0;
16598 							return TRUE;
16599 						}
16600 
16601 						// Look for parent with similar style format
16602 						if (format.merge_with_parents) {
16603 							dom.getParent(node.parentNode, function(parent) {
16604 								if (matchNode(parent, name, vars)) {
16605 									dom.remove(node, 1);
16606 									node = 0;
16607 									return TRUE;
16608 								}
16609 							});
16610 						}
16611 
16612 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16613 						if (node && format.merge_siblings !== false) {
16614 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16615 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16616 						}
16617 					}
16618 				});
16619 			};
16620 
16621 			if (format) {
16622 				if (node) {
16623 					if (node.nodeType) {
16624 						rng = dom.createRng();
16625 						rng.setStartBefore(node);
16626 						rng.setEndAfter(node);
16627 						applyRngStyle(expandRng(rng, formatList), null, true);
16628 					} else {
16629 						applyRngStyle(node, null, true);
16630 					}
16631 				} else {
16632 					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16633 						// Obtain selection node before selection is unselected by applyRngStyle()
16634 						var curSelNode = ed.selection.getNode();
16635 
16636 						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
16637 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
16638 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
16639 							apply(formatList[0].defaultBlock);
16640 						}
16641 
16642 						// Apply formatting to selection
16643 						ed.selection.setRng(adjustSelectionToVisibleSelection());
16644 						bookmark = selection.getBookmark();
16645 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
16646 
16647 						// Colored nodes should be underlined so that the color of the underline matches the text color.
16648 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
16649 							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
16650 							processUnderlineAndColor(curSelNode);
16651 						}
16652 
16653 						selection.moveToBookmark(bookmark);
16654 						moveStart(selection.getRng(TRUE));
16655 						ed.nodeChanged();
16656 					} else
16657 						performCaretAction('apply', name, vars);
16658 				}
16659 			}
16660 		};
16661 
16662 		function remove(name, vars, node) {
16663 			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
16664 
16665 			// Merges the styles for each node
16666 			function process(node) {
16667 				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
16668 
16669 				// Node has a contentEditable value
16670 				if (node.nodeType === 1 && getContentEditable(node)) {
16671 					lastContentEditable = contentEditable;
16672 					contentEditable = getContentEditable(node) === "true";
16673 					hasContentEditableState = true; // We don't want to wrap the container only it's children
16674 				}
16675 
16676 				// Grab the children first since the nodelist might be changed
16677 				children = tinymce.grep(node.childNodes);
16678 
16679 				// Process current node
16680 				if (contentEditable && !hasContentEditableState) {
16681 					for (i = 0, l = formatList.length; i < l; i++) {
16682 						if (removeFormat(formatList[i], vars, node, node))
16683 							break;
16684 					}
16685 				}
16686 
16687 				// Process the children
16688 				if (format.deep) {
16689 					if (children.length) {					
16690 						for (i = 0, l = children.length; i < l; i++)
16691 							process(children[i]);
16692 
16693 						if (hasContentEditableState) {
16694 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16695 						}
16696 					}
16697 				}
16698 			};
16699 
16700 			function findFormatRoot(container) {
16701 				var formatRoot;
16702 
16703 				// Find format root
16704 				each(getParents(container.parentNode).reverse(), function(parent) {
16705 					var format;
16706 
16707 					// Find format root element
16708 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
16709 						// Is the node matching the format we are looking for
16710 						format = matchNode(parent, name, vars);
16711 						if (format && format.split !== false)
16712 							formatRoot = parent;
16713 					}
16714 				});
16715 
16716 				return formatRoot;
16717 			};
16718 
16719 			function wrapAndSplit(format_root, container, target, split) {
16720 				var parent, clone, lastClone, firstClone, i, formatRootParent;
16721 
16722 				// Format root found then clone formats and split it
16723 				if (format_root) {
16724 					formatRootParent = format_root.parentNode;
16725 
16726 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
16727 						clone = dom.clone(parent, FALSE);
16728 
16729 						for (i = 0; i < formatList.length; i++) {
16730 							if (removeFormat(formatList[i], vars, clone, clone)) {
16731 								clone = 0;
16732 								break;
16733 							}
16734 						}
16735 
16736 						// Build wrapper node
16737 						if (clone) {
16738 							if (lastClone)
16739 								clone.appendChild(lastClone);
16740 
16741 							if (!firstClone)
16742 								firstClone = clone;
16743 
16744 							lastClone = clone;
16745 						}
16746 					}
16747 
16748 					// Never split block elements if the format is mixed
16749 					if (split && (!format.mixed || !isBlock(format_root)))
16750 						container = dom.split(format_root, container);
16751 
16752 					// Wrap container in cloned formats
16753 					if (lastClone) {
16754 						target.parentNode.insertBefore(lastClone, target);
16755 						firstClone.appendChild(target);
16756 					}
16757 				}
16758 
16759 				return container;
16760 			};
16761 
16762 			function splitToFormatRoot(container) {
16763 				return wrapAndSplit(findFormatRoot(container), container, container, true);
16764 			};
16765 
16766 			function unwrap(start) {
16767 				var node = dom.get(start ? '_start' : '_end'),
16768 					out = node[start ? 'firstChild' : 'lastChild'];
16769 
16770 				// If the end is placed within the start the result will be removed
16771 				// So this checks if the out node is a bookmark node if it is it
16772 				// checks for another more suitable node
16773 				if (isBookmarkNode(out))
16774 					out = out[start ? 'firstChild' : 'lastChild'];
16775 
16776 				dom.remove(node, true);
16777 
16778 				return out;
16779 			};
16780 
16781 			function removeRngStyle(rng) {
16782 				var startContainer, endContainer, node;
16783 
16784 				rng = expandRng(rng, formatList, TRUE);
16785 
16786 				if (format.split) {
16787 					startContainer = getContainer(rng, TRUE);
16788 					endContainer = getContainer(rng);
16789 
16790 					if (startContainer != endContainer) {
16791 						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
16792 						// This will happen if you tripple click a table cell and use remove formatting
16793 						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
16794 							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
16795 						}
16796 
16797 						// Wrap start/end nodes in span element since these might be cloned/moved
16798 						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
16799 						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
16800 
16801 						// Split start/end
16802 						splitToFormatRoot(startContainer);
16803 						splitToFormatRoot(endContainer);
16804 
16805 						// Unwrap start/end to get real elements again
16806 						startContainer = unwrap(TRUE);
16807 						endContainer = unwrap();
16808 					} else
16809 						startContainer = endContainer = splitToFormatRoot(startContainer);
16810 
16811 					// Update range positions since they might have changed after the split operations
16812 					rng.startContainer = startContainer.parentNode;
16813 					rng.startOffset = nodeIndex(startContainer);
16814 					rng.endContainer = endContainer.parentNode;
16815 					rng.endOffset = nodeIndex(endContainer) + 1;
16816 				}
16817 
16818 				// Remove items between start/end
16819 				rangeUtils.walk(rng, function(nodes) {
16820 					each(nodes, function(node) {
16821 						process(node);
16822 
16823 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
16824 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
16825 							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
16826 						}
16827 					});
16828 				});
16829 			};
16830 
16831 			// Handle node
16832 			if (node) {
16833 				if (node.nodeType) {
16834 					rng = dom.createRng();
16835 					rng.setStartBefore(node);
16836 					rng.setEndAfter(node);
16837 					removeRngStyle(rng);
16838 				} else {
16839 					removeRngStyle(node);
16840 				}
16841 
16842 				return;
16843 			}
16844 
16845 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16846 				bookmark = selection.getBookmark();
16847 				removeRngStyle(selection.getRng(TRUE));
16848 				selection.moveToBookmark(bookmark);
16849 
16850 				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
16851 				if (format.inline && match(name, vars, selection.getStart())) {
16852 					moveStart(selection.getRng(true));
16853 				}
16854 
16855 				ed.nodeChanged();
16856 			} else
16857 				performCaretAction('remove', name, vars);
16858 		};
16859 
16860 		function toggle(name, vars, node) {
16861 			var fmt = get(name);
16862 
16863 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
16864 				remove(name, vars, node);
16865 			else
16866 				apply(name, vars, node);
16867 		};
16868 
16869 		function matchNode(node, name, vars, similar) {
16870 			var formatList = get(name), format, i, classes;
16871 
16872 			function matchItems(node, format, item_name) {
16873 				var key, value, items = format[item_name], i;
16874 
16875 				// Custom match
16876 				if (format.onmatch) {
16877 					return format.onmatch(node, format, item_name);
16878 				}
16879 
16880 				// Check all items
16881 				if (items) {
16882 					// Non indexed object
16883 					if (items.length === undef) {
16884 						for (key in items) {
16885 							if (items.hasOwnProperty(key)) {
16886 								if (item_name === 'attributes')
16887 									value = dom.getAttrib(node, key);
16888 								else
16889 									value = getStyle(node, key);
16890 
16891 								if (similar && !value && !format.exact)
16892 									return;
16893 
16894 								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
16895 									return;
16896 							}
16897 						}
16898 					} else {
16899 						// Only one match needed for indexed arrays
16900 						for (i = 0; i < items.length; i++) {
16901 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
16902 								return format;
16903 						}
16904 					}
16905 				}
16906 
16907 				return format;
16908 			};
16909 
16910 			if (formatList && node) {
16911 				// Check each format in list
16912 				for (i = 0; i < formatList.length; i++) {
16913 					format = formatList[i];
16914 
16915 					// Name name, attributes, styles and classes
16916 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
16917 						// Match classes
16918 						if (classes = format.classes) {
16919 							for (i = 0; i < classes.length; i++) {
16920 								if (!dom.hasClass(node, classes[i]))
16921 									return;
16922 							}
16923 						}
16924 
16925 						return format;
16926 					}
16927 				}
16928 			}
16929 		};
16930 
16931 		function match(name, vars, node) {
16932 			var startNode;
16933 
16934 			function matchParents(node) {
16935 				// Find first node with similar format settings
16936 				node = dom.getParent(node, function(node) {
16937 					return !!matchNode(node, name, vars, true);
16938 				});
16939 
16940 				// Do an exact check on the similar format element
16941 				return matchNode(node, name, vars);
16942 			};
16943 
16944 			// Check specified node
16945 			if (node)
16946 				return matchParents(node);
16947 
16948 			// Check selected node
16949 			node = selection.getNode();
16950 			if (matchParents(node))
16951 				return TRUE;
16952 
16953 			// Check start node if it's different
16954 			startNode = selection.getStart();
16955 			if (startNode != node) {
16956 				if (matchParents(startNode))
16957 					return TRUE;
16958 			}
16959 
16960 			return FALSE;
16961 		};
16962 
16963 		function matchAll(names, vars) {
16964 			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
16965 
16966 			// Check start of selection for formats
16967 			startElement = selection.getStart();
16968 			dom.getParent(startElement, function(node) {
16969 				var i, name;
16970 
16971 				for (i = 0; i < names.length; i++) {
16972 					name = names[i];
16973 
16974 					if (!checkedMap[name] && matchNode(node, name, vars)) {
16975 						checkedMap[name] = true;
16976 						matchedFormatNames.push(name);
16977 					}
16978 				}
16979 			}, dom.getRoot());
16980 
16981 			return matchedFormatNames;
16982 		};
16983 
16984 		function canApply(name) {
16985 			var formatList = get(name), startNode, parents, i, x, selector;
16986 
16987 			if (formatList) {
16988 				startNode = selection.getStart();
16989 				parents = getParents(startNode);
16990 
16991 				for (x = formatList.length - 1; x >= 0; x--) {
16992 					selector = formatList[x].selector;
16993 
16994 					// Format is not selector based, then always return TRUE
16995 					if (!selector)
16996 						return TRUE;
16997 
16998 					for (i = parents.length - 1; i >= 0; i--) {
16999 						if (dom.is(parents[i], selector))
17000 							return TRUE;
17001 					}
17002 				}
17003 			}
17004 
17005 			return FALSE;
17006 		};
17007 
17008 		function formatChanged(formats, callback) {
17009 			var currentFormats;
17010 
17011 			// Setup format node change logic
17012 			if (!formatChangeData) {
17013 				formatChangeData = {};
17014 				currentFormats = {};
17015 
17016 				ed.onNodeChange.addToTop(function(ed, cm, node) {
17017 					var parents = getParents(node), matchedFormats = {};
17018 
17019 					// Check for new formats
17020 					each(formatChangeData, function(callbacks, format) {
17021 						each(parents, function(node) {
17022 							if (matchNode(node, format, {}, true)) {
17023 								if (!currentFormats[format]) {
17024 									// Execute callbacks
17025 									each(callbacks, function(callback) {
17026 										callback(true, {node: node, format: format, parents: parents});
17027 									});
17028 
17029 									currentFormats[format] = callbacks;
17030 								}
17031 
17032 								matchedFormats[format] = callbacks;
17033 								return false;
17034 							}
17035 						});
17036 					});
17037 
17038 					// Check if current formats still match
17039 					each(currentFormats, function(callbacks, format) {
17040 						if (!matchedFormats[format]) {
17041 							delete currentFormats[format];
17042 
17043 							each(callbacks, function(callback) {
17044 								callback(false, {node: node, format: format, parents: parents});
17045 							});
17046 						}
17047 					});
17048 				});
17049 			}
17050 
17051 			// Add format listeners
17052 			each(formats.split(','), function(format) {
17053 				if (!formatChangeData[format]) {
17054 					formatChangeData[format] = [];
17055 				}
17056 
17057 				formatChangeData[format].push(callback);
17058 			});
17059 
17060 			return this;
17061 		};
17062 
17063 		// Expose to public
17064 		tinymce.extend(this, {
17065 			get : get,
17066 			register : register,
17067 			apply : apply,
17068 			remove : remove,
17069 			toggle : toggle,
17070 			match : match,
17071 			matchAll : matchAll,
17072 			matchNode : matchNode,
17073 			canApply : canApply,
17074 			formatChanged: formatChanged
17075 		});
17076 
17077 		// Initialize
17078 		defaultFormats();
17079 		addKeyboardShortcuts();
17080 
17081 		// Private functions
17082 
17083 		function matchName(node, format) {
17084 			// Check for inline match
17085 			if (isEq(node, format.inline))
17086 				return TRUE;
17087 
17088 			// Check for block match
17089 			if (isEq(node, format.block))
17090 				return TRUE;
17091 
17092 			// Check for selector match
17093 			if (format.selector)
17094 				return dom.is(node, format.selector);
17095 		};
17096 
17097 		function isEq(str1, str2) {
17098 			str1 = str1 || '';
17099 			str2 = str2 || '';
17100 
17101 			str1 = '' + (str1.nodeName || str1);
17102 			str2 = '' + (str2.nodeName || str2);
17103 
17104 			return str1.toLowerCase() == str2.toLowerCase();
17105 		};
17106 
17107 		function getStyle(node, name) {
17108 			var styleVal = dom.getStyle(node, name);
17109 
17110 			// Force the format to hex
17111 			if (name == 'color' || name == 'backgroundColor')
17112 				styleVal = dom.toHex(styleVal);
17113 
17114 			// Opera will return bold as 700
17115 			if (name == 'fontWeight' && styleVal == 700)
17116 				styleVal = 'bold';
17117 
17118 			return '' + styleVal;
17119 		};
17120 
17121 		function replaceVars(value, vars) {
17122 			if (typeof(value) != "string")
17123 				value = value(vars);
17124 			else if (vars) {
17125 				value = value.replace(/%(\w+)/g, function(str, name) {
17126 					return vars[name] || str;
17127 				});
17128 			}
17129 
17130 			return value;
17131 		};
17132 
17133 		function isWhiteSpaceNode(node) {
17134 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
17135 		};
17136 
17137 		function wrap(node, name, attrs) {
17138 			var wrapper = dom.create(name, attrs);
17139 
17140 			node.parentNode.insertBefore(wrapper, node);
17141 			wrapper.appendChild(node);
17142 
17143 			return wrapper;
17144 		};
17145 
17146 		function expandRng(rng, format, remove) {
17147 			var sibling, lastIdx, leaf, endPoint,
17148 				startContainer = rng.startContainer,
17149 				startOffset = rng.startOffset,
17150 				endContainer = rng.endContainer,
17151 				endOffset = rng.endOffset;
17152 
17153 			// This function walks up the tree if there is no siblings before/after the node
17154 			function findParentContainer(start) {
17155 				var container, parent, child, sibling, siblingName, root;
17156 
17157 				container = parent = start ? startContainer : endContainer;
17158 				siblingName = start ? 'previousSibling' : 'nextSibling';
17159 				root = dom.getRoot();
17160 
17161 				// If it's a text node and the offset is inside the text
17162 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
17163 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
17164 						return container;
17165 					}
17166 				}
17167 
17168 				for (;;) {
17169 					// Stop expanding on block elements
17170 					if (!format[0].block_expand && isBlock(parent))
17171 						return parent;
17172 
17173 					// Walk left/right
17174 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
17175 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
17176 							return parent;
17177 						}
17178 					}
17179 
17180 					// Check if we can move up are we at root level or body level
17181 					if (parent.parentNode == root) {
17182 						container = parent;
17183 						break;
17184 					}
17185 
17186 					parent = parent.parentNode;
17187 				}
17188 
17189 				return container;
17190 			};
17191 
17192 			// This function walks down the tree to find the leaf at the selection.
17193 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
17194 			function findLeaf(node, offset) {
17195 				if (offset === undef)
17196 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17197 				while (node && node.hasChildNodes()) {
17198 					node = node.childNodes[offset];
17199 					if (node)
17200 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17201 				}
17202 				return { node: node, offset: offset };
17203 			}
17204 
17205 			// If index based start position then resolve it
17206 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
17207 				lastIdx = startContainer.childNodes.length - 1;
17208 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
17209 
17210 				if (startContainer.nodeType == 3)
17211 					startOffset = 0;
17212 			}
17213 
17214 			// If index based end position then resolve it
17215 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
17216 				lastIdx = endContainer.childNodes.length - 1;
17217 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
17218 
17219 				if (endContainer.nodeType == 3)
17220 					endOffset = endContainer.nodeValue.length;
17221 			}
17222 
17223 			// Expands the node to the closes contentEditable false element if it exists
17224 			function findParentContentEditable(node) {
17225 				var parent = node;
17226 
17227 				while (parent) {
17228 					if (parent.nodeType === 1 && getContentEditable(parent)) {
17229 						return getContentEditable(parent) === "false" ? parent : node;
17230 					}
17231 
17232 					parent = parent.parentNode;
17233 				}
17234 
17235 				return node;
17236 			};
17237 
17238 			function findWordEndPoint(container, offset, start) {
17239 				var walker, node, pos, lastTextNode;
17240 
17241 				function findSpace(node, offset) {
17242 					var pos, pos2, str = node.nodeValue;
17243 
17244 					if (typeof(offset) == "undefined") {
17245 						offset = start ? str.length : 0;
17246 					}
17247 
17248 					if (start) {
17249 						pos = str.lastIndexOf(' ', offset);
17250 						pos2 = str.lastIndexOf('\u00a0', offset);
17251 						pos = pos > pos2 ? pos : pos2;
17252 
17253 						// Include the space on remove to avoid tag soup
17254 						if (pos !== -1 && !remove) {
17255 							pos++;
17256 						}
17257 					} else {
17258 						pos = str.indexOf(' ', offset);
17259 						pos2 = str.indexOf('\u00a0', offset);
17260 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
17261 					}
17262 
17263 					return pos;
17264 				};
17265 
17266 				if (container.nodeType === 3) {
17267 					pos = findSpace(container, offset);
17268 
17269 					if (pos !== -1) {
17270 						return {container : container, offset : pos};
17271 					}
17272 
17273 					lastTextNode = container;
17274 				}
17275 
17276 				// Walk the nodes inside the block
17277 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
17278 				while (node = walker[start ? 'prev' : 'next']()) {
17279 					if (node.nodeType === 3) {
17280 						lastTextNode = node;
17281 						pos = findSpace(node);
17282 
17283 						if (pos !== -1) {
17284 							return {container : node, offset : pos};
17285 						}
17286 					} else if (isBlock(node)) {
17287 						break;
17288 					}
17289 				}
17290 
17291 				if (lastTextNode) {
17292 					if (start) {
17293 						offset = 0;
17294 					} else {
17295 						offset = lastTextNode.length;
17296 					}
17297 
17298 					return {container: lastTextNode, offset: offset};
17299 				}
17300 			};
17301 
17302 			function findSelectorEndPoint(container, sibling_name) {
17303 				var parents, i, y, curFormat;
17304 
17305 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
17306 					container = container[sibling_name];
17307 
17308 				parents = getParents(container);
17309 				for (i = 0; i < parents.length; i++) {
17310 					for (y = 0; y < format.length; y++) {
17311 						curFormat = format[y];
17312 
17313 						// If collapsed state is set then skip formats that doesn't match that
17314 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
17315 							continue;
17316 
17317 						if (dom.is(parents[i], curFormat.selector))
17318 							return parents[i];
17319 					}
17320 				}
17321 
17322 				return container;
17323 			};
17324 
17325 			function findBlockEndPoint(container, sibling_name, sibling_name2) {
17326 				var node;
17327 
17328 				// Expand to block of similar type
17329 				if (!format[0].wrapper)
17330 					node = dom.getParent(container, format[0].block);
17331 
17332 				// Expand to first wrappable block element or any block element
17333 				if (!node)
17334 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
17335 
17336 				// Exclude inner lists from wrapping
17337 				if (node && format[0].wrapper)
17338 					node = getParents(node, 'ul,ol').reverse()[0] || node;
17339 
17340 				// Didn't find a block element look for first/last wrappable element
17341 				if (!node) {
17342 					node = container;
17343 
17344 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
17345 						node = node[sibling_name];
17346 
17347 						// Break on BR but include it will be removed later on
17348 						// we can't remove it now since we need to check if it can be wrapped
17349 						if (isEq(node, 'br'))
17350 							break;
17351 					}
17352 				}
17353 
17354 				return node || container;
17355 			};
17356 
17357 			// Expand to closest contentEditable element
17358 			startContainer = findParentContentEditable(startContainer);
17359 			endContainer = findParentContentEditable(endContainer);
17360 
17361 			// Exclude bookmark nodes if possible
17362 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
17363 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
17364 				startContainer = startContainer.nextSibling || startContainer;
17365 
17366 				if (startContainer.nodeType == 3)
17367 					startOffset = 0;
17368 			}
17369 
17370 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
17371 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
17372 				endContainer = endContainer.previousSibling || endContainer;
17373 
17374 				if (endContainer.nodeType == 3)
17375 					endOffset = endContainer.length;
17376 			}
17377 
17378 			if (format[0].inline) {
17379 				if (rng.collapsed) {
17380 					// Expand left to closest word boundery
17381 					endPoint = findWordEndPoint(startContainer, startOffset, true);
17382 					if (endPoint) {
17383 						startContainer = endPoint.container;
17384 						startOffset = endPoint.offset;
17385 					}
17386 
17387 					// Expand right to closest word boundery
17388 					endPoint = findWordEndPoint(endContainer, endOffset);
17389 					if (endPoint) {
17390 						endContainer = endPoint.container;
17391 						endOffset = endPoint.offset;
17392 					}
17393 				}
17394 
17395 				// Avoid applying formatting to a trailing space.
17396 				leaf = findLeaf(endContainer, endOffset);
17397 				if (leaf.node) {
17398 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
17399 						leaf = findLeaf(leaf.node.previousSibling);
17400 
17401 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
17402 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
17403 
17404 						if (leaf.offset > 1) {
17405 							endContainer = leaf.node;
17406 							endContainer.splitText(leaf.offset - 1);
17407 						}
17408 					}
17409 				}
17410 			}
17411 
17412 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
17413 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
17414 			// This will reduce the number of wrapper elements that needs to be created
17415 			// Move start point up the tree
17416 			if (format[0].inline || format[0].block_expand) {
17417 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
17418 					startContainer = findParentContainer(true);
17419 				}
17420 
17421 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
17422 					endContainer = findParentContainer();
17423 				}
17424 			}
17425 
17426 			// Expand start/end container to matching selector
17427 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
17428 				// Find new startContainer/endContainer if there is better one
17429 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
17430 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
17431 			}
17432 
17433 			// Expand start/end container to matching block element or text node
17434 			if (format[0].block || format[0].selector) {
17435 				// Find new startContainer/endContainer if there is better one
17436 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
17437 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
17438 
17439 				// Non block element then try to expand up the leaf
17440 				if (format[0].block) {
17441 					if (!isBlock(startContainer))
17442 						startContainer = findParentContainer(true);
17443 
17444 					if (!isBlock(endContainer))
17445 						endContainer = findParentContainer();
17446 				}
17447 			}
17448 
17449 			// Setup index for startContainer
17450 			if (startContainer.nodeType == 1) {
17451 				startOffset = nodeIndex(startContainer);
17452 				startContainer = startContainer.parentNode;
17453 			}
17454 
17455 			// Setup index for endContainer
17456 			if (endContainer.nodeType == 1) {
17457 				endOffset = nodeIndex(endContainer) + 1;
17458 				endContainer = endContainer.parentNode;
17459 			}
17460 
17461 			// Return new range like object
17462 			return {
17463 				startContainer : startContainer,
17464 				startOffset : startOffset,
17465 				endContainer : endContainer,
17466 				endOffset : endOffset
17467 			};
17468 		}
17469 
17470 		function removeFormat(format, vars, node, compare_node) {
17471 			var i, attrs, stylesModified;
17472 
17473 			// Check if node matches format
17474 			if (!matchName(node, format))
17475 				return FALSE;
17476 
17477 			// Should we compare with format attribs and styles
17478 			if (format.remove != 'all') {
17479 				// Remove styles
17480 				each(format.styles, function(value, name) {
17481 					value = replaceVars(value, vars);
17482 
17483 					// Indexed array
17484 					if (typeof(name) === 'number') {
17485 						name = value;
17486 						compare_node = 0;
17487 					}
17488 
17489 					if (!compare_node || isEq(getStyle(compare_node, name), value))
17490 						dom.setStyle(node, name, '');
17491 
17492 					stylesModified = 1;
17493 				});
17494 
17495 				// Remove style attribute if it's empty
17496 				if (stylesModified && dom.getAttrib(node, 'style') == '') {
17497 					node.removeAttribute('style');
17498 					node.removeAttribute('data-mce-style');
17499 				}
17500 
17501 				// Remove attributes
17502 				each(format.attributes, function(value, name) {
17503 					var valueOut;
17504 
17505 					value = replaceVars(value, vars);
17506 
17507 					// Indexed array
17508 					if (typeof(name) === 'number') {
17509 						name = value;
17510 						compare_node = 0;
17511 					}
17512 
17513 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
17514 						// Keep internal classes
17515 						if (name == 'class') {
17516 							value = dom.getAttrib(node, name);
17517 							if (value) {
17518 								// Build new class value where everything is removed except the internal prefixed classes
17519 								valueOut = '';
17520 								each(value.split(/\s+/), function(cls) {
17521 									if (/mce\w+/.test(cls))
17522 										valueOut += (valueOut ? ' ' : '') + cls;
17523 								});
17524 
17525 								// We got some internal classes left
17526 								if (valueOut) {
17527 									dom.setAttrib(node, name, valueOut);
17528 									return;
17529 								}
17530 							}
17531 						}
17532 
17533 						// IE6 has a bug where the attribute doesn't get removed correctly
17534 						if (name == "class")
17535 							node.removeAttribute('className');
17536 
17537 						// Remove mce prefixed attributes
17538 						if (MCE_ATTR_RE.test(name))
17539 							node.removeAttribute('data-mce-' + name);
17540 
17541 						node.removeAttribute(name);
17542 					}
17543 				});
17544 
17545 				// Remove classes
17546 				each(format.classes, function(value) {
17547 					value = replaceVars(value, vars);
17548 
17549 					if (!compare_node || dom.hasClass(compare_node, value))
17550 						dom.removeClass(node, value);
17551 				});
17552 
17553 				// Check for non internal attributes
17554 				attrs = dom.getAttribs(node);
17555 				for (i = 0; i < attrs.length; i++) {
17556 					if (attrs[i].nodeName.indexOf('_') !== 0)
17557 						return FALSE;
17558 				}
17559 			}
17560 
17561 			// Remove the inline child if it's empty for example <b> or <span>
17562 			if (format.remove != 'none') {
17563 				removeNode(node, format);
17564 				return TRUE;
17565 			}
17566 		};
17567 
17568 		function removeNode(node, format) {
17569 			var parentNode = node.parentNode, rootBlockElm;
17570 
17571 			function find(node, next, inc) {
17572 				node = getNonWhiteSpaceSibling(node, next, inc);
17573 
17574 				return !node || (node.nodeName == 'BR' || isBlock(node));
17575 			};
17576 
17577 			if (format.block) {
17578 				if (!forcedRootBlock) {
17579 					// Append BR elements if needed before we remove the block
17580 					if (isBlock(node) && !isBlock(parentNode)) {
17581 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
17582 							node.insertBefore(dom.create('br'), node.firstChild);
17583 
17584 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
17585 							node.appendChild(dom.create('br'));
17586 					}
17587 				} else {
17588 					// Wrap the block in a forcedRootBlock if we are at the root of document
17589 					if (parentNode == dom.getRoot()) {
17590 						if (!format.list_block || !isEq(node, format.list_block)) {
17591 							each(tinymce.grep(node.childNodes), function(node) {
17592 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
17593 									if (!rootBlockElm)
17594 										rootBlockElm = wrap(node, forcedRootBlock);
17595 									else
17596 										rootBlockElm.appendChild(node);
17597 								} else
17598 									rootBlockElm = 0;
17599 							});
17600 						}
17601 					}
17602 				}
17603 			}
17604 
17605 			// Never remove nodes that isn't the specified inline element if a selector is specified too
17606 			if (format.selector && format.inline && !isEq(format.inline, node))
17607 				return;
17608 
17609 			dom.remove(node, 1);
17610 		};
17611 
17612 		function getNonWhiteSpaceSibling(node, next, inc) {
17613 			if (node) {
17614 				next = next ? 'nextSibling' : 'previousSibling';
17615 
17616 				for (node = inc ? node : node[next]; node; node = node[next]) {
17617 					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
17618 						return node;
17619 				}
17620 			}
17621 		};
17622 
17623 		function isBookmarkNode(node) {
17624 			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
17625 		};
17626 
17627 		function mergeSiblings(prev, next) {
17628 			var marker, sibling, tmpSibling;
17629 
17630 			function compareElements(node1, node2) {
17631 				// Not the same name
17632 				if (node1.nodeName != node2.nodeName)
17633 					return FALSE;
17634 
17635 				function getAttribs(node) {
17636 					var attribs = {};
17637 
17638 					each(dom.getAttribs(node), function(attr) {
17639 						var name = attr.nodeName.toLowerCase();
17640 
17641 						// Don't compare internal attributes or style
17642 						if (name.indexOf('_') !== 0 && name !== 'style')
17643 							attribs[name] = dom.getAttrib(node, name);
17644 					});
17645 
17646 					return attribs;
17647 				};
17648 
17649 				function compareObjects(obj1, obj2) {
17650 					var value, name;
17651 
17652 					for (name in obj1) {
17653 						// Obj1 has item obj2 doesn't have
17654 						if (obj1.hasOwnProperty(name)) {
17655 							value = obj2[name];
17656 
17657 							// Obj2 doesn't have obj1 item
17658 							if (value === undef)
17659 								return FALSE;
17660 
17661 							// Obj2 item has a different value
17662 							if (obj1[name] != value)
17663 								return FALSE;
17664 
17665 							// Delete similar value
17666 							delete obj2[name];
17667 						}
17668 					}
17669 
17670 					// Check if obj 2 has something obj 1 doesn't have
17671 					for (name in obj2) {
17672 						// Obj2 has item obj1 doesn't have
17673 						if (obj2.hasOwnProperty(name))
17674 							return FALSE;
17675 					}
17676 
17677 					return TRUE;
17678 				};
17679 
17680 				// Attribs are not the same
17681 				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
17682 					return FALSE;
17683 
17684 				// Styles are not the same
17685 				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
17686 					return FALSE;
17687 
17688 				return TRUE;
17689 			};
17690 
17691 			function findElementSibling(node, sibling_name) {
17692 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
17693 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
17694 						return node;
17695 
17696 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
17697 						return sibling;
17698 				}
17699 
17700 				return node;
17701 			};
17702 
17703 			// Check if next/prev exists and that they are elements
17704 			if (prev && next) {
17705 				// If previous sibling is empty then jump over it
17706 				prev = findElementSibling(prev, 'previousSibling');
17707 				next = findElementSibling(next, 'nextSibling');
17708 
17709 				// Compare next and previous nodes
17710 				if (compareElements(prev, next)) {
17711 					// Append nodes between
17712 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
17713 						tmpSibling = sibling;
17714 						sibling = sibling.nextSibling;
17715 						prev.appendChild(tmpSibling);
17716 					}
17717 
17718 					// Remove next node
17719 					dom.remove(next);
17720 
17721 					// Move children into prev node
17722 					each(tinymce.grep(next.childNodes), function(node) {
17723 						prev.appendChild(node);
17724 					});
17725 
17726 					return prev;
17727 				}
17728 			}
17729 
17730 			return next;
17731 		};
17732 
17733 		function isTextBlock(name) {
17734 			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
17735 		};
17736 
17737 		function getContainer(rng, start) {
17738 			var container, offset, lastIdx, walker;
17739 
17740 			container = rng[start ? 'startContainer' : 'endContainer'];
17741 			offset = rng[start ? 'startOffset' : 'endOffset'];
17742 
17743 			if (container.nodeType == 1) {
17744 				lastIdx = container.childNodes.length - 1;
17745 
17746 				if (!start && offset)
17747 					offset--;
17748 
17749 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
17750 			}
17751 
17752 			// If start text node is excluded then walk to the next node
17753 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
17754 				container = new TreeWalker(container, ed.getBody()).next() || container;
17755 			}
17756 
17757 			// If end text node is excluded then walk to the previous node
17758 			if (container.nodeType === 3 && !start && offset === 0) {
17759 				container = new TreeWalker(container, ed.getBody()).prev() || container;
17760 			}
17761 
17762 			return container;
17763 		};
17764 
17765 		function performCaretAction(type, name, vars) {
17766 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
17767 
17768 			// Creates a caret container bogus element
17769 			function createCaretContainer(fill) {
17770 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
17771 
17772 				if (fill) {
17773 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
17774 				}
17775 
17776 				return caretContainer;
17777 			};
17778 
17779 			function isCaretContainerEmpty(node, nodes) {
17780 				while (node) {
17781 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
17782 						return false;
17783 					}
17784 
17785 					// Collect nodes
17786 					if (nodes && node.nodeType === 1) {
17787 						nodes.push(node);
17788 					}
17789 
17790 					node = node.firstChild;
17791 				}
17792 
17793 				return true;
17794 			};
17795 			
17796 			// Returns any parent caret container element
17797 			function getParentCaretContainer(node) {
17798 				while (node) {
17799 					if (node.id === caretContainerId) {
17800 						return node;
17801 					}
17802 
17803 					node = node.parentNode;
17804 				}
17805 			};
17806 
17807 			// Finds the first text node in the specified node
17808 			function findFirstTextNode(node) {
17809 				var walker;
17810 
17811 				if (node) {
17812 					walker = new TreeWalker(node, node);
17813 
17814 					for (node = walker.current(); node; node = walker.next()) {
17815 						if (node.nodeType === 3) {
17816 							return node;
17817 						}
17818 					}
17819 				}
17820 			};
17821 
17822 			// Removes the caret container for the specified node or all on the current document
17823 			function removeCaretContainer(node, move_caret) {
17824 				var child, rng;
17825 
17826 				if (!node) {
17827 					node = getParentCaretContainer(selection.getStart());
17828 
17829 					if (!node) {
17830 						while (node = dom.get(caretContainerId)) {
17831 							removeCaretContainer(node, false);
17832 						}
17833 					}
17834 				} else {
17835 					rng = selection.getRng(true);
17836 
17837 					if (isCaretContainerEmpty(node)) {
17838 						if (move_caret !== false) {
17839 							rng.setStartBefore(node);
17840 							rng.setEndBefore(node);
17841 						}
17842 
17843 						dom.remove(node);
17844 					} else {
17845 						child = findFirstTextNode(node);
17846 
17847 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
17848 							child = child.deleteData(0, 1);
17849 						}
17850 
17851 						dom.remove(node, 1);
17852 					}
17853 
17854 					selection.setRng(rng);
17855 				}
17856 			};
17857 			
17858 			// Applies formatting to the caret postion
17859 			function applyCaretFormat() {
17860 				var rng, caretContainer, textNode, offset, bookmark, container, text;
17861 
17862 				rng = selection.getRng(true);
17863 				offset = rng.startOffset;
17864 				container = rng.startContainer;
17865 				text = container.nodeValue;
17866 
17867 				caretContainer = getParentCaretContainer(selection.getStart());
17868 				if (caretContainer) {
17869 					textNode = findFirstTextNode(caretContainer);
17870 				}
17871 
17872 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
17873 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
17874 					// Get bookmark of caret position
17875 					bookmark = selection.getBookmark();
17876 
17877 					// Collapse bookmark range (WebKit)
17878 					rng.collapse(true);
17879 
17880 					// Expand the range to the closest word and split it at those points
17881 					rng = expandRng(rng, get(name));
17882 					rng = rangeUtils.split(rng);
17883 
17884 					// Apply the format to the range
17885 					apply(name, vars, rng);
17886 
17887 					// Move selection back to caret position
17888 					selection.moveToBookmark(bookmark);
17889 				} else {
17890 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
17891 						caretContainer = createCaretContainer(true);
17892 						textNode = caretContainer.firstChild;
17893 
17894 						rng.insertNode(caretContainer);
17895 						offset = 1;
17896 
17897 						apply(name, vars, caretContainer);
17898 					} else {
17899 						apply(name, vars, caretContainer);
17900 					}
17901 
17902 					// Move selection to text node
17903 					selection.setCursorLocation(textNode, offset);
17904 				}
17905 			};
17906 
17907 			function removeCaretFormat() {
17908 				var rng = selection.getRng(true), container, offset, bookmark,
17909 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
17910 
17911 				container = rng.startContainer;
17912 				offset = rng.startOffset;
17913 				node = container;
17914 
17915 				if (container.nodeType == 3) {
17916 					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
17917 						hasContentAfter = true;
17918 					}
17919 
17920 					node = node.parentNode;
17921 				}
17922 
17923 				while (node) {
17924 					if (matchNode(node, name, vars)) {
17925 						formatNode = node;
17926 						break;
17927 					}
17928 
17929 					if (node.nextSibling) {
17930 						hasContentAfter = true;
17931 					}
17932 
17933 					parents.push(node);
17934 					node = node.parentNode;
17935 				}
17936 
17937 				// Node doesn't have the specified format
17938 				if (!formatNode) {
17939 					return;
17940 				}
17941 
17942 				// Is there contents after the caret then remove the format on the element
17943 				if (hasContentAfter) {
17944 					// Get bookmark of caret position
17945 					bookmark = selection.getBookmark();
17946 
17947 					// Collapse bookmark range (WebKit)
17948 					rng.collapse(true);
17949 
17950 					// Expand the range to the closest word and split it at those points
17951 					rng = expandRng(rng, get(name), true);
17952 					rng = rangeUtils.split(rng);
17953 
17954 					// Remove the format from the range
17955 					remove(name, vars, rng);
17956 
17957 					// Move selection back to caret position
17958 					selection.moveToBookmark(bookmark);
17959 				} else {
17960 					caretContainer = createCaretContainer();
17961 
17962 					node = caretContainer;
17963 					for (i = parents.length - 1; i >= 0; i--) {
17964 						node.appendChild(dom.clone(parents[i], false));
17965 						node = node.firstChild;
17966 					}
17967 
17968 					// Insert invisible character into inner most format element
17969 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
17970 					node = node.firstChild;
17971 
17972 					// Insert caret container after the formated node
17973 					dom.insertAfter(caretContainer, formatNode);
17974 
17975 					// Move selection to text node
17976 					selection.setCursorLocation(node, 1);
17977 				}
17978 			};
17979 
17980 			// Checks if the parent caret container node isn't empty if that is the case it
17981 			// will remove the bogus state on all children that isn't empty
17982 			function unmarkBogusCaretParents() {
17983 				var i, caretContainer, node;
17984 
17985 				caretContainer = getParentCaretContainer(selection.getStart());
17986 				if (caretContainer && !dom.isEmpty(caretContainer)) {
17987 					tinymce.walk(caretContainer, function(node) {
17988 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
17989 							dom.setAttrib(node, 'data-mce-bogus', null);
17990 						}
17991 					}, 'childNodes');
17992 				}
17993 			};
17994 
17995 			// Only bind the caret events once
17996 			if (!self._hasCaretEvents) {
17997 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
17998 				ed.onBeforeGetContent.addToTop(function() {
17999 					var nodes = [], i;
18000 
18001 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
18002 						// Mark children
18003 						i = nodes.length;
18004 						while (i--) {
18005 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
18006 						}
18007 					}
18008 				});
18009 
18010 				// Remove caret container on mouse up and on key up
18011 				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
18012 					ed[name].addToTop(function() {
18013 						removeCaretContainer();
18014 						unmarkBogusCaretParents();
18015 					});
18016 				});
18017 
18018 				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
18019 				ed.onKeyDown.addToTop(function(ed, e) {
18020 					var keyCode = e.keyCode;
18021 
18022 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
18023 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
18024 					}
18025 
18026 					unmarkBogusCaretParents();
18027 				});
18028 
18029 				// Remove bogus state if they got filled by contents using editor.selection.setContent
18030 				selection.onSetContent.add(unmarkBogusCaretParents);
18031 
18032 				self._hasCaretEvents = true;
18033 			}
18034 
18035 			// Do apply or remove caret format
18036 			if (type == "apply") {
18037 				applyCaretFormat();
18038 			} else {
18039 				removeCaretFormat();
18040 			}
18041 		};
18042 
18043 		function moveStart(rng) {
18044 			var container = rng.startContainer,
18045 					offset = rng.startOffset, isAtEndOfText,
18046 					walker, node, nodes, tmpNode;
18047 
18048 			// Convert text node into index if possible
18049 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
18050 				// Get the parent container location and walk from there
18051 				offset = nodeIndex(container);
18052 				container = container.parentNode;
18053 				isAtEndOfText = true;
18054 			}
18055 
18056 			// Move startContainer/startOffset in to a suitable node
18057 			if (container.nodeType == 1) {
18058 				nodes = container.childNodes;
18059 				container = nodes[Math.min(offset, nodes.length - 1)];
18060 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
18061 
18062 				// If offset is at end of the parent node walk to the next one
18063 				if (offset > nodes.length - 1 || isAtEndOfText)
18064 					walker.next();
18065 
18066 				for (node = walker.current(); node; node = walker.next()) {
18067 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
18068 						// IE has a "neat" feature where it moves the start node into the closest element
18069 						// we can avoid this by inserting an element before it and then remove it after we set the selection
18070 						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
18071 						node.parentNode.insertBefore(tmpNode, node);
18072 
18073 						// Set selection and remove tmpNode
18074 						rng.setStart(node, 0);
18075 						selection.setRng(rng);
18076 						dom.remove(tmpNode);
18077 
18078 						return;
18079 					}
18080 				}
18081 			}
18082 		};
18083 	};
18084 })(tinymce);
18085 
18086 tinymce.onAddEditor.add(function(tinymce, ed) {
18087 	var filters, fontSizes, dom, settings = ed.settings;
18088 
18089 	function replaceWithSpan(node, styles) {
18090 		tinymce.each(styles, function(value, name) {
18091 			if (value)
18092 				dom.setStyle(node, name, value);
18093 		});
18094 
18095 		dom.rename(node, 'span');
18096 	};
18097 
18098 	function convert(editor, params) {
18099 		dom = editor.dom;
18100 
18101 		if (settings.convert_fonts_to_spans) {
18102 			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
18103 				filters[node.nodeName.toLowerCase()](ed.dom, node);
18104 			});
18105 		}
18106 	};
18107 
18108 	if (settings.inline_styles) {
18109 		fontSizes = tinymce.explode(settings.font_size_legacy_values);
18110 
18111 		filters = {
18112 			font : function(dom, node) {
18113 				replaceWithSpan(node, {
18114 					backgroundColor : node.style.backgroundColor,
18115 					color : node.color,
18116 					fontFamily : node.face,
18117 					fontSize : fontSizes[parseInt(node.size, 10) - 1]
18118 				});
18119 			},
18120 
18121 			u : function(dom, node) {
18122 				replaceWithSpan(node, {
18123 					textDecoration : 'underline'
18124 				});
18125 			},
18126 
18127 			strike : function(dom, node) {
18128 				replaceWithSpan(node, {
18129 					textDecoration : 'line-through'
18130 				});
18131 			}
18132 		};
18133 
18134 		ed.onPreProcess.add(convert);
18135 		ed.onSetContent.add(convert);
18136 
18137 		ed.onInit.add(function() {
18138 			ed.selection.onSetContent.add(convert);
18139 		});
18140 	}
18141 });
18142 
18143 (function(tinymce) {
18144 	var TreeWalker = tinymce.dom.TreeWalker;
18145 
18146 	tinymce.EnterKey = function(editor) {
18147 		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18148 
18149 		function handleEnterKey(evt) {
18150 			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
18151 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
18152 
18153 			// Returns true if the block can be split into two blocks or not
18154 			function canSplitBlock(node) {
18155 				return node &&
18156 					dom.isBlock(node) &&
18157 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
18158 					!/^(fixed|absolute)/i.test(node.style.position) && 
18159 					dom.getContentEditable(node) !== "true";
18160 			};
18161 
18162 			// Renders empty block on IE
18163 			function renderBlockOnIE(block) {
18164 				var oldRng;
18165 
18166 				if (tinymce.isIE && dom.isBlock(block)) {
18167 					oldRng = selection.getRng();
18168 					block.appendChild(dom.create('span', null, '\u00a0'));
18169 					selection.select(block);
18170 					block.lastChild.outerHTML = '';
18171 					selection.setRng(oldRng);
18172 				}
18173 			};
18174 
18175 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
18176 			function trimInlineElementsOnLeftSideOfBlock(block) {
18177 				var node = block, firstChilds = [], i;
18178 
18179 				// Find inner most first child ex: <p><i><b>*</b></i></p>
18180 				while (node = node.firstChild) {
18181 					if (dom.isBlock(node)) {
18182 						return;
18183 					}
18184 
18185 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18186 						firstChilds.push(node);
18187 					}
18188 				}
18189 
18190 				i = firstChilds.length;
18191 				while (i--) {
18192 					node = firstChilds[i];
18193 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
18194 						dom.remove(node);
18195 					}
18196 				}
18197 			};
18198 			
18199 			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
18200 			function moveToCaretPosition(root) {
18201 				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
18202 
18203 				rng = dom.createRng();
18204 
18205 				if (root.hasChildNodes()) {
18206 					walker = new TreeWalker(root, root);
18207 
18208 					while (node = walker.current()) {
18209 						if (node.nodeType == 3) {
18210 							rng.setStart(node, 0);
18211 							rng.setEnd(node, 0);
18212 							break;
18213 						}
18214 
18215 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18216 							rng.setStartBefore(node);
18217 							rng.setEndBefore(node);
18218 							break;
18219 						}
18220 
18221 						lastNode = node;
18222 						node = walker.next();
18223 					}
18224 
18225 					if (!node) {
18226 						rng.setStart(lastNode, 0);
18227 						rng.setEnd(lastNode, 0);
18228 					}
18229 				} else {
18230 					if (root.nodeName == 'BR') {
18231 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
18232 							// Trick on older IE versions to render the caret before the BR between two lists
18233 							if (!documentMode || documentMode < 9) {
18234 								tempElm = dom.create('br');
18235 								root.parentNode.insertBefore(tempElm, root);
18236 							}
18237 
18238 							rng.setStartBefore(root);
18239 							rng.setEndBefore(root);
18240 						} else {
18241 							rng.setStartAfter(root);
18242 							rng.setEndAfter(root);
18243 						}
18244 					} else {
18245 						rng.setStart(root, 0);
18246 						rng.setEnd(root, 0);
18247 					}
18248 				}
18249 
18250 				selection.setRng(rng);
18251 
18252 				// Remove tempElm created for old IE:s
18253 				dom.remove(tempElm);
18254 
18255 				viewPort = dom.getViewPort(editor.getWin());
18256 
18257 				// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
18258 				y = dom.getPos(root).y;
18259 				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
18260 					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
18261 				}
18262 			};
18263 
18264 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
18265 			// This function will also copy any text formatting from the parent block and add it to the new one
18266 			function createNewBlock(name) {
18267 				var node = container, block, clonedNode, caretNode;
18268 
18269 				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18270 				caretNode = block;
18271 
18272 				// Clone any parent styles
18273 				if (settings.keep_styles !== false) {
18274 					do {
18275 						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
18276 							clonedNode = node.cloneNode(false);
18277 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
18278 
18279 							if (block.hasChildNodes()) {
18280 								clonedNode.appendChild(block.firstChild);
18281 								block.appendChild(clonedNode);
18282 							} else {
18283 								caretNode = clonedNode;
18284 								block.appendChild(clonedNode);
18285 							}
18286 						}
18287 					} while (node = node.parentNode);
18288 				}
18289 
18290 				// BR is needed in empty blocks on non IE browsers
18291 				if (!tinymce.isIE) {
18292 					caretNode.innerHTML = '<br>';
18293 				}
18294 
18295 				return block;
18296 			};
18297 
18298 			// Returns true/false if the caret is at the start/end of the parent block element
18299 			function isCaretAtStartOrEndOfBlock(start) {
18300 				var walker, node, name;
18301 
18302 				// Caret is in the middle of a text node like "a|b"
18303 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
18304 					return false;
18305 				}
18306 
18307 				// If after the last element in block node edge case for #5091
18308 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
18309 					return true;
18310 				}
18311 
18312 				// If the caret if before the first element in parentBlock
18313 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
18314 					return true;
18315 				}
18316 
18317 				// Caret can be before/after a table
18318 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
18319 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
18320 				}
18321 
18322 				// Walk the DOM and look for text nodes or non empty elements
18323 				walker = new TreeWalker(container, parentBlock);
18324 	
18325 				// If caret is in beginning or end of a text block then jump to the next/previous node
18326 				if (container.nodeType == 3) {
18327 					if (start && offset == 0) {
18328 						walker.prev();
18329 					} else if (!start && offset == container.nodeValue.length) {
18330 						walker.next();
18331 					}
18332 				}
18333 
18334 				while (node = walker.current()) {
18335 					if (node.nodeType === 1) {
18336 						// Ignore bogus elements
18337 						if (!node.getAttribute('data-mce-bogus')) {
18338 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
18339 							name = node.nodeName.toLowerCase();
18340 							if (nonEmptyElementsMap[name] && name !== 'br') {
18341 								return false;
18342 							}
18343 						}
18344 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
18345 						return false;
18346 					}
18347 
18348 					if (start) {
18349 						walker.prev();
18350 					} else {
18351 						walker.next();
18352 					}
18353 				}
18354 
18355 				return true;
18356 			};
18357 
18358 			// Wraps any text nodes or inline elements in the specified forced root block name
18359 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
18360 				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
18361 
18362 				// Not in a block element or in a table cell or caption
18363 				parentBlock = dom.getParent(container, dom.isBlock);
18364 				if (!parentBlock || !canSplitBlock(parentBlock)) {
18365 					parentBlock = parentBlock || editableRoot;
18366 
18367 					if (!parentBlock.hasChildNodes()) {
18368 						newBlock = dom.create(blockName);
18369 						parentBlock.appendChild(newBlock);
18370 						rng.setStart(newBlock, 0);
18371 						rng.setEnd(newBlock, 0);
18372 						return newBlock;
18373 					}
18374 
18375 					// Find parent that is the first child of parentBlock
18376 					node = container;
18377 					while (node.parentNode != parentBlock) {
18378 						node = node.parentNode;
18379 					}
18380 
18381 					// Loop left to find start node start wrapping at
18382 					while (node && !dom.isBlock(node)) {
18383 						startNode = node;
18384 						node = node.previousSibling;
18385 					}
18386 
18387 					if (startNode) {
18388 						newBlock = dom.create(blockName);
18389 						startNode.parentNode.insertBefore(newBlock, startNode);
18390 
18391 						// Start wrapping until we hit a block
18392 						node = startNode;
18393 						while (node && !dom.isBlock(node)) {
18394 							next = node.nextSibling;
18395 							newBlock.appendChild(node);
18396 							node = next;
18397 						}
18398 
18399 						// Restore range to it's past location
18400 						rng.setStart(container, offset);
18401 						rng.setEnd(container, offset);
18402 					}
18403 				}
18404 
18405 				return container;
18406 			};
18407 
18408 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
18409 			function handleEmptyListItem() {
18410 				function isFirstOrLastLi(first) {
18411 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
18412 
18413 					// Find first/last element since there might be whitespace there
18414 					while (node) {
18415 						if (node.nodeType == 1) {
18416 							break;
18417 						}
18418 
18419 						node = node[first ? 'nextSibling' : 'previousSibling'];
18420 					}
18421 
18422 					return node === parentBlock;
18423 				};
18424 
18425 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
18426 
18427 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
18428 					// Is first and last list item then replace the OL/UL with a text block
18429 					dom.replace(newBlock, containerBlock);
18430 				} else if (isFirstOrLastLi(true)) {
18431 					// First LI in list then remove LI and add text block before list
18432 					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
18433 				} else if (isFirstOrLastLi()) {
18434 					// Last LI in list then temove LI and add text block after list
18435 					dom.insertAfter(newBlock, containerBlock);
18436 					renderBlockOnIE(newBlock);
18437 				} else {
18438 					// Middle LI in list the split the list and insert a text block in the middle
18439 					// Extract after fragment and insert it after the current block
18440 					tmpRng = rng.cloneRange();
18441 					tmpRng.setStartAfter(parentBlock);
18442 					tmpRng.setEndAfter(containerBlock);
18443 					fragment = tmpRng.extractContents();
18444 					dom.insertAfter(fragment, containerBlock);
18445 					dom.insertAfter(newBlock, containerBlock);
18446 				}
18447 
18448 				dom.remove(parentBlock);
18449 				moveToCaretPosition(newBlock);
18450 				undoManager.add();
18451 			};
18452 
18453 			// Walks the parent block to the right and look for BR elements
18454 			function hasRightSideBr() {
18455 				var walker = new TreeWalker(container, parentBlock), node;
18456 
18457 				while (node = walker.current()) {
18458 					if (node.nodeName == 'BR') {
18459 						return true;
18460 					}
18461 
18462 					node = walker.next();
18463 				}
18464 			}
18465 			
18466 			// Inserts a BR element if the forced_root_block option is set to false or empty string
18467 			function insertBr() {
18468 				var brElm, extraBr;
18469 
18470 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18471 					// Insert extra BR element at the end block elements
18472 					if (!tinymce.isIE && !hasRightSideBr()) {
18473 						brElm = dom.create('br')
18474 						rng.insertNode(brElm);
18475 						rng.setStartAfter(brElm);
18476 						rng.setEndAfter(brElm);
18477 						extraBr = true;
18478 					}
18479 				}
18480 
18481 				brElm = dom.create('br');
18482 				rng.insertNode(brElm);
18483 
18484 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18485 				if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18486 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18487 				}
18488 
18489 				if (!extraBr) {
18490 					rng.setStartAfter(brElm);
18491 					rng.setEndAfter(brElm);
18492 				} else {
18493 					rng.setStartBefore(brElm);
18494 					rng.setEndBefore(brElm);
18495 				}
18496 
18497 				selection.setRng(rng);
18498 				undoManager.add();
18499 			};
18500 
18501 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
18502 			function trimLeadingLineBreaks(node) {
18503 				do {
18504 					if (node.nodeType === 3) {
18505 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
18506 					}
18507 
18508 					node = node.firstChild;
18509 				} while (node);
18510 			};
18511 
18512 			function getEditableRoot(node) {
18513 				var root = dom.getRoot(), parent, editableRoot;
18514 
18515 				// Get all parents until we hit a non editable parent or the root
18516 				parent = node;
18517 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
18518 					if (dom.getContentEditable(parent) === "true") {
18519 						editableRoot = parent;
18520 					}
18521 
18522 					parent = parent.parentNode;
18523 				}
18524 				
18525 				return parent !== root ? editableRoot : root;
18526 			};
18527 
18528 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
18529 			function addBrToBlockIfNeeded(block) {
18530 				var lastChild;
18531 
18532 				// IE will render the blocks correctly other browsers needs a BR
18533 				if (!tinymce.isIE) {
18534 					block.normalize(); // Remove empty text nodes that got left behind by the extract
18535 
18536 					// Check if the block is empty or contains a floated last child
18537 					lastChild = block.lastChild;
18538 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
18539 						dom.add(block, 'br');
18540 					}
18541 				}
18542 			};
18543 
18544 			// Delete any selected contents
18545 			if (!rng.collapsed) {
18546 				editor.execCommand('Delete');
18547 				return;
18548 			}
18549 
18550 			// Event is blocked by some other handler for example the lists plugin
18551 			if (evt.isDefaultPrevented()) {
18552 				return;
18553 			}
18554 
18555 			// Setup range items and newBlockName
18556 			container = rng.startContainer;
18557 			offset = rng.startOffset;
18558 			newBlockName = settings.forced_root_block;
18559 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
18560 			documentMode = dom.doc.documentMode;
18561 
18562 			// Resolve node index
18563 			if (container.nodeType == 1 && container.hasChildNodes()) {
18564 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18565 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18566 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
18567 					offset = container.nodeValue.length;
18568 				} else {
18569 					offset = 0;
18570 				}
18571 			}
18572 
18573 			// Get editable root node normaly the body element but sometimes a div or span
18574 			editableRoot = getEditableRoot(container);
18575 
18576 			// If there is no editable root then enter is done inside a contentEditable false element
18577 			if (!editableRoot) {
18578 				return;
18579 			}
18580 
18581 			undoManager.beforeChange();
18582 
18583 			// If editable root isn't block nor the root of the editor
18584 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
18585 				if (!newBlockName || evt.shiftKey) {
18586 					insertBr();
18587 				}
18588 
18589 				return;
18590 			}
18591 
18592 			// Wrap the current node and it's sibling in a default block if it's needed.
18593 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
18594 			// This won't happen if root blocks are disabled or the shiftKey is pressed
18595 			if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
18596 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
18597 			}
18598 
18599 			// Find parent block and setup empty block paddings
18600 			parentBlock = dom.getParent(container, dom.isBlock);
18601 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
18602 
18603 			// Setup block names
18604 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18605 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18606 
18607 			// Handle enter inside an empty list item
18608 			if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
18609 				// Let the list plugin or browser handle nested lists for now
18610 				if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
18611 					return false;
18612 				}
18613 
18614 				handleEmptyListItem();
18615 				return;
18616 			}
18617 
18618 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
18619 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
18620 				if (!evt.shiftKey) {
18621 					insertBr();
18622 					return;
18623 				}
18624 			} else {
18625 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
18626 				if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
18627 					insertBr();
18628 					return;
18629 				}
18630 			}
18631 
18632 			// Default block name if it's not configured
18633 			newBlockName = newBlockName || 'P';
18634 
18635 			// Insert new block before/after the parent block depending on caret location
18636 			if (isCaretAtStartOrEndOfBlock()) {
18637 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
18638 				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
18639 					newBlock = createNewBlock(newBlockName);
18640 				} else {
18641 					newBlock = createNewBlock();
18642 				}
18643 
18644 				// Split the current container block element if enter is pressed inside an empty inner block element
18645 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
18646 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
18647 					newBlock = dom.split(containerBlock, parentBlock);
18648 				} else {
18649 					dom.insertAfter(newBlock, parentBlock);
18650 				}
18651 
18652 				moveToCaretPosition(newBlock);
18653 			} else if (isCaretAtStartOrEndOfBlock(true)) {
18654 				// Insert new block before
18655 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
18656 				renderBlockOnIE(newBlock);
18657 			} else {
18658 				// Extract after fragment and insert it after the current block
18659 				tmpRng = rng.cloneRange();
18660 				tmpRng.setEndAfter(parentBlock);
18661 				fragment = tmpRng.extractContents();
18662 				trimLeadingLineBreaks(fragment);
18663 				newBlock = fragment.firstChild;
18664 				dom.insertAfter(fragment, parentBlock);
18665 				trimInlineElementsOnLeftSideOfBlock(newBlock);
18666 				addBrToBlockIfNeeded(parentBlock);
18667 				moveToCaretPosition(newBlock);
18668 			}
18669 
18670 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
18671 			undoManager.add();
18672 		}
18673 
18674 		editor.onKeyDown.add(function(ed, evt) {
18675 			if (evt.keyCode == 13) {
18676 				if (handleEnterKey(evt) !== false) {
18677 					evt.preventDefault();
18678 				}
18679 			}
18680 		});
18681 	};
18682 })(tinymce);
18683 
18684